├── .gitignore ├── dist └── php-cs-fixer ├── src ├── Contracts │ ├── Formatter.php │ ├── Code.php │ ├── Arrayable.php │ ├── ObjectContainer.php │ ├── AssignableContainer.php │ └── DelegatesImports.php ├── References │ ├── This.php │ ├── SelfReference.php │ ├── StaticReference.php │ ├── StaticProperty.php │ ├── ObjectProperty.php │ ├── Variable.php │ ├── ClassReference.php │ ├── Reference.php │ └── ArrayKeyReference.php ├── Values │ ├── NullValue.php │ ├── BooleanValue.php │ ├── FloatValue.php │ ├── IntegerValue.php │ ├── StringValue.php │ ├── ArrayValue.php │ ├── AssignableValue.php │ ├── Value.php │ ├── AssociativeArrayValue.php │ └── Closure.php ├── Abstractions │ ├── DeclarativeCode.php │ ├── ImperativeCode.php │ └── Code.php ├── Comparisons │ ├── Equals.php │ ├── GreaterThan.php │ ├── LesserThan.php │ ├── NotEquals.php │ ├── EqualsStrict.php │ ├── OrComparison.php │ ├── AndComparison.php │ ├── NotEqualsStrict.php │ ├── GreaterThanEquals.php │ ├── LesserThanEquals.php │ ├── Not.php │ └── Comparison.php ├── ImperativeCode │ ├── ControlBlocks │ │ ├── DoWhileBlock.php │ │ ├── WhileBlock.php │ │ ├── ForeachBlock.php │ │ └── IfBlock.php │ ├── InvokeFunctionBlock.php │ ├── InstantiateBlock.php │ ├── ReturnBlock.php │ ├── InvokeMethodBlock.php │ ├── ThrowBlock.php │ ├── InvokeStaticMethodBlock.php │ ├── UseStatement.php │ ├── AssignBlock.php │ ├── ChainedMethodBlock.php │ ├── InvokeBlock.php │ └── Block.php ├── Concerns │ ├── HasName.php │ ├── HasNamespace.php │ ├── CanBeFinal.php │ ├── CanBeAbstract.php │ ├── CanBeStatic.php │ ├── DelegatesImports.php │ ├── HasDocBlock.php │ ├── HasValue.php │ ├── HasMethods.php │ ├── HasAccessModifier.php │ ├── HasArguments.php │ ├── HasProperties.php │ └── HasImports.php ├── DeclarativeCode │ ├── Constant.php │ ├── ClassConstant.php │ ├── Argument.php │ ├── ClassProperty.php │ ├── ClassMethod.php │ └── ClassTemplate.php ├── Enums │ ├── BaseEnum.php │ └── ClassAccess.php ├── Utilities │ └── Importable.php └── Formatters │ └── CsFixerFormatter.php ├── phpunit.xml ├── composer.json ├── tests ├── Unit │ ├── Declarative │ │ ├── ConstantTest.php │ │ ├── ClassConstantTest.php │ │ ├── UseStatementTest.php │ │ ├── ArgumentTest.php │ │ ├── ClassPropertyTest.php │ │ └── ClassMethodTest.php │ ├── TemplateTest.php │ ├── Imperative │ │ ├── ThrowBlockTest.php │ │ ├── InstantiateTest.php │ │ ├── ReturnTest.php │ │ ├── AssignTest.php │ │ ├── ForeachBlockTest.php │ │ ├── BlockTest.php │ │ ├── IfBlockTest.php │ │ ├── WhileBlockTest.php │ │ ├── ComparisonTest.php │ │ └── InvokeTest.php │ ├── Enums │ │ └── ClassAccessTest.php │ ├── ImportableTest.php │ └── ReferenceTest.php └── Concerns │ └── ImperativeCodeDataProviders.php ├── .github └── workflows │ └── php.yml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea 3 | coverage 4 | test.php 5 | generated.php -------------------------------------------------------------------------------- /dist/php-cs-fixer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shomisha/stubless/HEAD/dist/php-cs-fixer -------------------------------------------------------------------------------- /src/Contracts/Formatter.php: -------------------------------------------------------------------------------- 1 | raw = $raw; 12 | } 13 | 14 | public function getRaw() 15 | { 16 | return $this->raw; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Values/FloatValue.php: -------------------------------------------------------------------------------- 1 | raw = $raw; 12 | } 13 | 14 | public function getRaw() 15 | { 16 | return $this->raw; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Values/IntegerValue.php: -------------------------------------------------------------------------------- 1 | raw = $raw; 12 | } 13 | 14 | public function getRaw() 15 | { 16 | return $this->raw; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Values/StringValue.php: -------------------------------------------------------------------------------- 1 | raw = $raw; 12 | } 13 | 14 | public function getRaw() 15 | { 16 | return $this->raw; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Abstractions/DeclarativeCode.php: -------------------------------------------------------------------------------- 1 | getFormatter()->format( 10 | $this->getPrinter()->prettyPrintFile( 11 | $this->getPrintableNodes() 12 | ) 13 | ); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Comparisons/Equals.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/GreaterThan.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/LesserThan.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/NotEquals.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/EqualsStrict.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/OrComparison.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/AndComparison.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/NotEqualsStrict.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/GreaterThanEquals.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Comparisons/LesserThanEquals.php: -------------------------------------------------------------------------------- 1 | first->getAssignableValueExpression(), $this->second->getAssignableValueExpression()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/ImperativeCode/ControlBlocks/DoWhileBlock.php: -------------------------------------------------------------------------------- 1 | getPrintableConditionExpression(), 14 | $this->getPrintableBodyExpressions() 15 | ), 16 | ]; 17 | } 18 | } -------------------------------------------------------------------------------- /src/ImperativeCode/InvokeFunctionBlock.php: -------------------------------------------------------------------------------- 1 | name), 15 | $this->normalizedArguments() 16 | ) 17 | ]; 18 | } 19 | } -------------------------------------------------------------------------------- /src/Comparisons/Not.php: -------------------------------------------------------------------------------- 1 | first = $first; 14 | $this->second = Value::null(); 15 | } 16 | 17 | protected function getComparableNode(): Node 18 | { 19 | return new Node\Expr\BooleanNot($this->first->getAssignableValueExpression()); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Concerns/HasName.php: -------------------------------------------------------------------------------- 1 | name = $name; 12 | } 13 | 14 | public function getName(): string 15 | { 16 | return $this->name; 17 | } 18 | 19 | public function setName(string $name): self 20 | { 21 | $this->name = $name; 22 | 23 | return $this; 24 | } 25 | 26 | public static function name(string $name): self 27 | { 28 | return new static($name); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Concerns/HasNamespace.php: -------------------------------------------------------------------------------- 1 | namespace !== null; 15 | } 16 | 17 | public function getNamespace(): ?string 18 | { 19 | return $this->namespace; 20 | } 21 | 22 | public function setNamespace(?string $namespace): self 23 | { 24 | $this->namespace = $namespace; 25 | 26 | return $this; 27 | } 28 | } -------------------------------------------------------------------------------- /src/ImperativeCode/InstantiateBlock.php: -------------------------------------------------------------------------------- 1 | isImportable($class)) { 13 | $this->addImportable($class); 14 | } 15 | 16 | parent::__construct((string) $class, $arguments); 17 | } 18 | 19 | public function getInvokablePrintableNodes(): array 20 | { 21 | return [ 22 | new New_(new Name($this->name), $this->normalizedArguments()), 23 | ]; 24 | } 25 | } -------------------------------------------------------------------------------- /src/ImperativeCode/ReturnBlock.php: -------------------------------------------------------------------------------- 1 | value = $returnValue; 16 | } 17 | 18 | public function getPrintableNodes(): array 19 | { 20 | return [new Return_($this->value->getAssignableValueExpression())]; 21 | } 22 | 23 | public function getImportSubDelegates(): array 24 | { 25 | return [$this->value]; 26 | } 27 | } -------------------------------------------------------------------------------- /src/References/StaticProperty.php: -------------------------------------------------------------------------------- 1 | class = $class; 17 | } 18 | 19 | public function getPrintableNodes(): array 20 | { 21 | return [new StaticPropertyFetch(new Name($this->class->getName()), $this->name)]; 22 | } 23 | 24 | protected function getImportSubDelegates(): array 25 | { 26 | return [ 27 | $this->class 28 | ]; 29 | } 30 | } -------------------------------------------------------------------------------- /src/References/ObjectProperty.php: -------------------------------------------------------------------------------- 1 | variable = $object; 15 | $this->name = $name; 16 | } 17 | 18 | public function getPrintableNodes(): array 19 | { 20 | return [ 21 | new PropertyFetch($this->variable->getPrintableNodes()[0], $this->name), 22 | ]; 23 | } 24 | 25 | protected function getImportSubDelegates(): array 26 | { 27 | return [$this->variable]; 28 | } 29 | } -------------------------------------------------------------------------------- /src/DeclarativeCode/Constant.php: -------------------------------------------------------------------------------- 1 | value)) 19 | ? $this->value 20 | : null; 21 | 22 | return [new Const_Stmnt([ 23 | new Const_Node($this->name, BuilderHelpers::normalizeValue($value)) 24 | ])]; 25 | } 26 | } -------------------------------------------------------------------------------- /src/ImperativeCode/InvokeMethodBlock.php: -------------------------------------------------------------------------------- 1 | object = $object; 17 | } 18 | 19 | public function getInvokablePrintableNodes(): array 20 | { 21 | return [ 22 | new Expr\MethodCall( 23 | $this->object->getPrintableNodes()[0], 24 | $this->name, 25 | $this->normalizedArguments() 26 | ) 27 | ]; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Concerns/CanBeFinal.php: -------------------------------------------------------------------------------- 1 | isFinal(); 15 | } 16 | 17 | return $this->makeFinal($isFinal); 18 | } 19 | 20 | public function makeFinal($final = true): self 21 | { 22 | $this->isFinal = $final; 23 | 24 | return $this; 25 | } 26 | 27 | public function isFinal(): bool 28 | { 29 | return $this->isFinal; 30 | } 31 | 32 | public function makeBuilderFinal(Builder $builder): void 33 | { 34 | if ($this->isFinal) { 35 | $builder->makeFinal(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src 6 | 7 | 8 | 9 | 10 | ./tests/Unit 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/DeclarativeCode/ClassConstant.php: -------------------------------------------------------------------------------- 1 | access = $access ?? ClassAccess::PUBLIC(); 18 | } 19 | 20 | public function getPrintableNodes(): array 21 | { 22 | /** @var \PhpParser\Node\Stmt\Const_ $stmt */ 23 | $stmt = parent::getPrintableNodes()[0]; 24 | 25 | return [new ClassConst($stmt->consts, $this->access->getPhpParserAccessModifier())]; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Enums/BaseEnum.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | 14 | public function __toString() 15 | { 16 | return $this->value; 17 | } 18 | 19 | public function value(): string 20 | { 21 | return $this->value; 22 | } 23 | 24 | public static function fromString(string $value): self 25 | { 26 | if (!in_array($value, static::all())) { 27 | throw new \InvalidArgumentException( 28 | sprintf("Invalid enum value '%s' for enum %s", $value, static::class) 29 | ); 30 | } 31 | 32 | return new static($value); 33 | } 34 | 35 | abstract public static function all(): array; 36 | } -------------------------------------------------------------------------------- /src/ImperativeCode/ThrowBlock.php: -------------------------------------------------------------------------------- 1 | exception = $exception; 16 | } 17 | 18 | public function getPrintableNodes(): array 19 | { 20 | return [ 21 | new Throw_($this->exception->getAssignableValueExpression()) 22 | ]; 23 | } 24 | 25 | protected function getImportSubDelegates(): array 26 | { 27 | return $this->extractImportDelegatesFromArray([ 28 | $this->exception 29 | ]); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Concerns/CanBeAbstract.php: -------------------------------------------------------------------------------- 1 | isAbstract(); 15 | } 16 | 17 | return $this->makeAbstract($abstract); 18 | } 19 | 20 | public function makeAbstract($abstract = true): self 21 | { 22 | $this->isAbstract = $abstract; 23 | 24 | return $this; 25 | } 26 | 27 | public function isAbstract(): bool 28 | { 29 | return $this->isAbstract; 30 | } 31 | 32 | /** @param \PhpParser\Builder\Method|\PhpParser\Builder\Class_ $builder */ 33 | public function makeBuilderAbstract(Builder $builder): void 34 | { 35 | if ($this->isAbstract) { 36 | $builder->makeAbstract(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Concerns/CanBeStatic.php: -------------------------------------------------------------------------------- 1 | makeStatic($isStatic); 15 | } 16 | 17 | return $this->isStatic(); 18 | } 19 | 20 | public function makeStatic(bool $isStatic = true): self 21 | { 22 | $this->isStatic = $isStatic; 23 | 24 | return $this; 25 | } 26 | 27 | public function isStatic(): bool 28 | { 29 | return $this->isStatic; 30 | } 31 | 32 | /** @param \PhpParser\Builder\Method|\PhpParser\Builder\Property $builder */ 33 | public function makeBuilderStatic(Builder $builder): self 34 | { 35 | if ($this->isStatic) { 36 | $builder->makeStatic(); 37 | } 38 | 39 | return $this; 40 | } 41 | } -------------------------------------------------------------------------------- /src/ImperativeCode/InvokeStaticMethodBlock.php: -------------------------------------------------------------------------------- 1 | class = $class; 18 | } 19 | 20 | public function getInvokablePrintableNodes(): array 21 | { 22 | return [ 23 | new StaticCall(new Name($this->class->getName()), $this->name, $this->normalizedArguments()) 24 | ]; 25 | } 26 | 27 | public function getImportSubDelegates(): array 28 | { 29 | return array_merge( 30 | parent::getImportSubDelegates(), 31 | [$this->class] 32 | ); 33 | } 34 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shomisha/stubless", 3 | "description": "A package for generating PHP files without stubs.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Miša Ković", 9 | "email": "misa95kovic@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.4|^8.0", 14 | "ext-json": "*", 15 | "nikic/php-parser": "^4.10" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^9.4" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Shomisha\\Stubless\\": "src" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "Shomisha\\Stubless\\Test\\": "tests" 28 | } 29 | }, 30 | "scripts": { 31 | "test": "php vendor/bin/phpunit --bootstrap vendor/autoload.php --config phpunit.xml" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ImperativeCode/UseStatement.php: -------------------------------------------------------------------------------- 1 | name = $name; 17 | $this->as = $as; 18 | } 19 | 20 | public function getAs(): ?string 21 | { 22 | return $this->as; 23 | } 24 | 25 | public function setAs(string $as): self 26 | { 27 | $this->as = $as; 28 | 29 | return $this; 30 | } 31 | 32 | /** @return \PhpParser\Builder\Use_ */ 33 | public function getPrintableNodes(): array 34 | { 35 | $statement = $this->getFactory()->use($this->name); 36 | 37 | if ($this->as !== null) { 38 | $statement->as($this->as); 39 | } 40 | 41 | return [$this->convertBuilderToNode($statement)]; 42 | } 43 | } -------------------------------------------------------------------------------- /tests/Unit/Declarative/ConstantTest.php: -------------------------------------------------------------------------------- 1 | value(212); 14 | 15 | 16 | $printed = $constant->print(); 17 | 18 | 19 | $this->assertStringContainsString('const TEST = 212;', $printed); 20 | } 21 | 22 | /** @test */ 23 | public function user_can_get_constant_value() 24 | { 25 | $constant = Constant::name('TEST')->value(3.14); 26 | 27 | 28 | $value = $constant->getValue(); 29 | 30 | 31 | $this->assertEquals(3.14, $value); 32 | } 33 | 34 | /** @test */ 35 | public function user_cannot_set_invalid_values_to_constants() 36 | { 37 | $this->expectException(\InvalidArgumentException::class); 38 | 39 | Constant::name('TEST')->value(new \stdClass()); 40 | } 41 | } -------------------------------------------------------------------------------- /src/ImperativeCode/AssignBlock.php: -------------------------------------------------------------------------------- 1 | container = $container; 19 | $this->value = $value; 20 | } 21 | 22 | public function getPrintableNodes(): array 23 | { 24 | return [ 25 | new Assign( 26 | $this->container->getAssignableContainerExpression(), 27 | $this->value->getAssignableValueExpression() 28 | ) 29 | ]; 30 | } 31 | 32 | protected function getImportSubDelegates(): array 33 | { 34 | return [ 35 | $this->container, 36 | $this->value, 37 | ]; 38 | } 39 | } -------------------------------------------------------------------------------- /tests/Unit/TemplateTest.php: -------------------------------------------------------------------------------- 1 | value(15); 14 | 15 | 16 | $printed = $template->print(); 17 | 18 | 19 | $this->assertStringContainsString('const TEST = 15', $printed); 20 | } 21 | 22 | /** @test */ 23 | public function templates_can_save_themselves_to_the_filesystem() 24 | { 25 | $path = tempnam(sys_get_temp_dir(), 'print_test'); 26 | 27 | $template = Constant::name('ANOTHER_TEST')->value('ANOTHER VALUE'); 28 | 29 | 30 | $saved = $template->save($path); 31 | 32 | 33 | $this->assertTrue($saved); 34 | 35 | $savedContents = file_get_contents($path); 36 | $this->assertStringContainsString("const ANOTHER_TEST = 'ANOTHER VALUE'", $savedContents); 37 | 38 | unlink($path); 39 | } 40 | } -------------------------------------------------------------------------------- /src/Concerns/DelegatesImports.php: -------------------------------------------------------------------------------- 1 | getImports(); 15 | 16 | foreach ($this->getImportSubDelegates() as $subDelegate) { 17 | $imports = array_merge($imports, $subDelegate->getDelegatedImports()); 18 | } 19 | 20 | return $imports; 21 | } 22 | 23 | protected function getImportSubDelegates(): array 24 | { 25 | return []; 26 | } 27 | 28 | protected function extractImportDelegatesFromArray(array $potentialDelegates): array 29 | { 30 | return array_filter($potentialDelegates, function ($potentialDelegate) { 31 | return $potentialDelegate instanceof DelegatesImportsContract; 32 | }); 33 | } 34 | } -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer & Tests 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Setup PHP 18 | uses: shivammathur/setup-php@v2 19 | with: 20 | php-version: '7.4' 21 | 22 | - name: Validate composer.json and composer.lock 23 | run: composer validate 24 | 25 | - name: Cache Composer packages 26 | id: composer-cache 27 | uses: actions/cache@v2 28 | with: 29 | path: vendor 30 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-php- 33 | 34 | - name: Install dependencies 35 | if: steps.composer-cache.outputs.cache-hit != 'true' 36 | run: composer install --prefer-dist --no-progress --no-suggest 37 | 38 | - name: Run test suite 39 | run: composer run-script test 40 | -------------------------------------------------------------------------------- /src/Concerns/HasDocBlock.php: -------------------------------------------------------------------------------- 1 | getDocBlock(); 18 | } 19 | 20 | return $this->withDocBlock($docBlock); 21 | } 22 | 23 | public function withDocBlock(?string $docBlock): self 24 | { 25 | $this->docBlock = $docBlock; 26 | 27 | return $this; 28 | } 29 | 30 | public function getDocBlock(): ?string 31 | { 32 | return $this->docBlock; 33 | } 34 | 35 | protected function setDocBlockCommentOnBuilder(Builder $builder): void 36 | { 37 | if ($docComment = $this->getDocBlockComment()) { 38 | $builder->setDocComment($docComment); 39 | } 40 | } 41 | 42 | protected function getDocBlockComment(): ?Doc 43 | { 44 | if ($this->docBlock === null) { 45 | return null; 46 | } 47 | 48 | return new Doc("/**\n" . $this->docBlock . "\n*/"); 49 | } 50 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Shomisha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Values/ArrayValue.php: -------------------------------------------------------------------------------- 1 | elements = array_map(function ($element) { 15 | return Value::normalize($element); 16 | }, $raw); 17 | } 18 | 19 | public function getRaw() 20 | { 21 | return array_map(function (AssignableValue $value) { 22 | if ($value instanceof Value) { 23 | return $value->getRaw(); 24 | } 25 | 26 | return $value; 27 | }, $this->elements); 28 | } 29 | 30 | protected function getPrintableRaw() 31 | { 32 | return array_map(function (AssignableValue $value) { 33 | return $value->getPrintableNodes()[0]; 34 | }, $this->elements); 35 | } 36 | 37 | public function getPrintableArrayExpr(): Expr 38 | { 39 | return $this->getPrintableNodes()[0]; 40 | } 41 | 42 | protected function getImportSubDelegates(): array 43 | { 44 | return $this->extractImportDelegatesFromArray($this->elements); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Utilities/Importable.php: -------------------------------------------------------------------------------- 1 | fqcn = $fullName; 20 | $this->alias = $alias; 21 | $this->baseName = $this->guessBaseName($fullName); 22 | $this->use = new UseStatement($fullName, $alias); 23 | } 24 | 25 | public function __toString() 26 | { 27 | return $this->getShortName(); 28 | } 29 | 30 | public function getFullName(): string 31 | { 32 | return $this->fqcn; 33 | } 34 | 35 | public function getImportStatement(): UseStatement 36 | { 37 | return $this->use; 38 | } 39 | 40 | public function getShortName(): string 41 | { 42 | return $this->alias ?? $this->baseName; 43 | } 44 | 45 | private function guessBaseName(string $fullName): string 46 | { 47 | $exploded = explode('\\', $fullName); 48 | 49 | return array_pop($exploded); 50 | } 51 | } -------------------------------------------------------------------------------- /src/References/Variable.php: -------------------------------------------------------------------------------- 1 | getName()); 19 | } 20 | 21 | public function getPrintableNodes(): array 22 | { 23 | return [new \PhpParser\Node\Expr\Variable($this->name)]; 24 | } 25 | 26 | public function getAssignableContainerExpression(): Expr 27 | { 28 | return $this->getPrintableNodes()[0]; 29 | } 30 | 31 | public function getAssignableValueExpression(): Expr 32 | { 33 | return $this->getPrintableNodes()[0]; 34 | } 35 | 36 | public function getPrintableArrayExpr(): Expr 37 | { 38 | return $this->getPrintableNodes()[0]; 39 | } 40 | 41 | public function getObjectContainerExpression(): Expr 42 | { 43 | return $this->getPrintableNodes()[0]; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Abstractions/ImperativeCode.php: -------------------------------------------------------------------------------- 1 | getPrintableNodes()[0]; 20 | }, $this->getDelegatedImports()); 21 | 22 | $expressions = $this->normalizeNodesToExpressions($this->getPrintableNodes()); 23 | 24 | return $this->getFormatter()->format( 25 | $this->getPrinter()->prettyPrintFile( 26 | [...array_values($importNodes), ...$expressions] 27 | ) 28 | ); 29 | } 30 | 31 | protected function normalizeNodesToExpressions(array $nodes): array 32 | { 33 | return array_map(function (Node $node) { 34 | if ($node instanceof Node\Stmt) { 35 | return $node; 36 | } 37 | 38 | return new Expression($node); 39 | }, $nodes); 40 | } 41 | } -------------------------------------------------------------------------------- /src/ImperativeCode/ChainedMethodBlock.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 15 | } 16 | 17 | public function print(): string 18 | { 19 | return $this->parent->print(); 20 | } 21 | 22 | protected function getInvokablePrintableNodes(): array 23 | { 24 | return [ 25 | new MethodCall( 26 | $this->parent->getInvokablePrintableNodes()[0], 27 | $this->name, 28 | $this->normalizedArguments() 29 | ) 30 | ]; 31 | } 32 | 33 | public function getDelegatedImports(): array 34 | { 35 | return $this->parent->getDelegatedImports(); 36 | } 37 | 38 | protected function getChainedImports(): array 39 | { 40 | $imports = $this->getImports(); 41 | 42 | if ($this->hasChainedMethod()) { 43 | $imports = array_merge($this->chainedMethod->getChainedImports()); 44 | } 45 | 46 | foreach ($this->extractImportDelegatesFromArray($this->arguments) as $argument) { 47 | $imports = array_merge($imports, $argument->getDelegatedImports()); 48 | } 49 | 50 | return $imports; 51 | } 52 | } -------------------------------------------------------------------------------- /src/References/ClassReference.php: -------------------------------------------------------------------------------- 1 | name = $class; 18 | 19 | if ($this->isImportable($class)) { 20 | $this->addImportable($class); 21 | } 22 | } 23 | 24 | public static function normalize($value): ClassReference 25 | { 26 | if ($value instanceof ClassReference) { 27 | return $value; 28 | } 29 | 30 | if (is_string($value)) { 31 | return new self($value); 32 | } 33 | 34 | if ($value instanceof Importable) { 35 | return new self($value); 36 | } 37 | 38 | throw new \InvalidArgumentException("Value cannot be safely normalized to a ClassReference instance."); 39 | } 40 | 41 | public function getPrintableNodes(): array 42 | { 43 | return [ 44 | new ClassConstFetch(new Name($this->name), 'class'), 45 | ]; 46 | } 47 | 48 | public function getImportSubDelegates(): array 49 | { 50 | return []; 51 | } 52 | 53 | public function getAssignableValueExpression(): Expr 54 | { 55 | return $this->getPrintableNodes()[0]; 56 | } 57 | } -------------------------------------------------------------------------------- /src/Values/AssignableValue.php: -------------------------------------------------------------------------------- 1 | getPrintableNodes()[0]; 14 | } 15 | 16 | public static function normalize($value): AssignableValue 17 | { 18 | if ($value instanceof AssignableValue) { 19 | return $value; 20 | } 21 | 22 | if (is_null($value)) { 23 | return Value::null(); 24 | } 25 | 26 | if (is_string($value)) { 27 | return Value::string($value); 28 | } 29 | 30 | if ($value instanceof Importable) { 31 | return Value::string($value)->addImportable($value); 32 | } 33 | 34 | if (is_integer($value)) { 35 | return Value::integer($value); 36 | } 37 | 38 | if (is_float($value)) { 39 | return Value::float($value); 40 | } 41 | 42 | if (is_array($value)) { 43 | return Value::array($value); 44 | } 45 | 46 | if (is_bool($value)) { 47 | return Value::boolean($value); 48 | } 49 | 50 | throw new \InvalidArgumentException(sprintf( 51 | "Object of type %s cannot be safely normalized to an instance of %s", 52 | get_class($value), 53 | self::class 54 | )); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Enums/ClassAccess.php: -------------------------------------------------------------------------------- 1 | phpParserAccessModifier = $phpParserAccessModifier; 15 | } 16 | 17 | public function getPhpParserAccessModifier(): int 18 | { 19 | return $this->phpParserAccessModifier; 20 | } 21 | 22 | public static function fromString(string $value): BaseEnum 23 | { 24 | switch ($value) { 25 | case 'public': 26 | return self::PUBLIC(); 27 | case 'protected': 28 | return self::PROTECTED(); 29 | case 'private': 30 | return self::PRIVATE(); 31 | } 32 | 33 | throw new \InvalidArgumentException("Invalid class access provided: {$value}"); 34 | } 35 | 36 | public static function PUBLIC(): self 37 | { 38 | return new self('public', Class_::MODIFIER_PUBLIC); 39 | } 40 | 41 | public static function PROTECTED(): self 42 | { 43 | return new self('protected', Class_::MODIFIER_PROTECTED); 44 | } 45 | 46 | public static function PRIVATE(): self 47 | { 48 | return new self('private', Class_::MODIFIER_PRIVATE); 49 | } 50 | 51 | public static function all(): array 52 | { 53 | return [ 54 | self::PUBLIC(), 55 | self::PROTECTED(), 56 | self::PRIVATE(), 57 | ]; 58 | } 59 | } -------------------------------------------------------------------------------- /src/Concerns/HasValue.php: -------------------------------------------------------------------------------- 1 | getValue(); 20 | } 21 | 22 | return $this->setValue($value); 23 | } 24 | 25 | public function getValue() 26 | { 27 | return $this->value; 28 | } 29 | 30 | public function setValue($value): self 31 | { 32 | if ($value instanceof Importable) { 33 | $value = Reference::classReference($value); 34 | } 35 | 36 | $this->assertValueIsValid($value); 37 | 38 | $this->value = $value; 39 | 40 | return $this; 41 | } 42 | 43 | protected function assertValueIsValid($value) 44 | { 45 | if (is_object($value) && !($value instanceof ClassReference)) { 46 | throw new \InvalidArgumentException(sprintf("You cannot assign objects as values to %s.", static::class)); 47 | } 48 | } 49 | 50 | protected function getValueExpr(): ?Expr 51 | { 52 | if (!isset($this->value)) { 53 | return null; 54 | } 55 | 56 | if ($this->value instanceof ClassReference) { 57 | return $this->value->getPrintableNodes()[0]; 58 | } 59 | 60 | return $this->getFactory()->val($this->value); 61 | } 62 | } -------------------------------------------------------------------------------- /src/References/Reference.php: -------------------------------------------------------------------------------- 1 | condition = $condition; 19 | $this->body = $body; 20 | } 21 | 22 | public function do(?ImperativeCode $body): self 23 | { 24 | $this->body = $body; 25 | 26 | return $this; 27 | } 28 | 29 | public function getPrintableNodes(): array 30 | { 31 | return [ 32 | new While_( 33 | $this->getPrintableConditionExpression(), 34 | $this->getPrintableBodyExpressions(), 35 | ) 36 | ]; 37 | } 38 | 39 | protected function getPrintableConditionExpression(): Expr 40 | { 41 | return $this->condition->getAssignableValueExpression(); 42 | } 43 | 44 | protected function getPrintableBodyExpressions(): array 45 | { 46 | if ($this->body === null) { 47 | return []; 48 | } 49 | 50 | return $this->normalizeNodesToExpressions( 51 | $this->body->getPrintableNodes() 52 | ); 53 | } 54 | 55 | protected function getImportSubDelegates(): array 56 | { 57 | return $this->extractImportDelegatesFromArray([ 58 | $this->condition, 59 | $this->body 60 | ]); 61 | } 62 | } -------------------------------------------------------------------------------- /src/Values/Value.php: -------------------------------------------------------------------------------- 1 | getRaw(); 55 | } 56 | 57 | public function getAssignableValueExpression(): Expr 58 | { 59 | return $this->getFactory()->val($this->getPrintableRaw()); 60 | } 61 | 62 | public function getPrintableNodes(): array 63 | { 64 | return [ 65 | $this->getAssignableValueExpression() 66 | ]; 67 | } 68 | } -------------------------------------------------------------------------------- /tests/Unit/Declarative/ClassConstantTest.php: -------------------------------------------------------------------------------- 1 | value(15); 15 | 16 | 17 | $printed = $constant->print(); 18 | 19 | 20 | $this->assertStringContainsString('public const TEST = 15;', $printed); 21 | } 22 | 23 | /** @test */ 24 | public function user_can_create_public_class_constants() 25 | { 26 | $constant = ClassConstant::name('TEST')->value(false); 27 | $constant->makePublic(); 28 | 29 | 30 | $printed = $constant->print(); 31 | 32 | 33 | $this->assertStringContainsString('public const TEST = false;', $printed); 34 | } 35 | 36 | /** @test */ 37 | public function user_can_create_protected_class_constants() 38 | { 39 | $constant = ClassConstant::name('PROTECTED_CONSTANT')->value('test'); 40 | 41 | $constant->makeProtected(); 42 | 43 | 44 | $printed = $constant->print(); 45 | 46 | 47 | $this->assertStringContainsString('protected const PROTECTED_CONSTANT = \'test\';', $printed); 48 | } 49 | 50 | /** @test */ 51 | public function user_can_create_private_class_constants() 52 | { 53 | $constant = ClassConstant::name('TEST')->value(12); 54 | 55 | $constant->makePrivate(); 56 | 57 | 58 | $printed = $constant->print(); 59 | 60 | 61 | $this->assertStringContainsString('private const TEST = 12;', $printed); 62 | } 63 | } -------------------------------------------------------------------------------- /src/Concerns/HasMethods.php: -------------------------------------------------------------------------------- 1 | getMethods(); 19 | } 20 | 21 | return $this->withMethods($methods); 22 | } 23 | 24 | public function addMethod(ClassMethod $method): self 25 | { 26 | $this->methods[$method->getName()] = $method; 27 | 28 | return $this; 29 | } 30 | 31 | public function removeMethod(string $name): self 32 | { 33 | unset($this->methods[$name]); 34 | 35 | return $this; 36 | } 37 | 38 | /** @param \Shomisha\Stubless\DeclarativeCode\ClassMethod[] $methods */ 39 | public function withMethods(array $methods): self 40 | { 41 | $this->validateArrayElements($methods, ClassMethod::class); 42 | 43 | $this->methods = []; 44 | 45 | foreach ($methods as $method) { 46 | $this->addMethod($method); 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | /** @return \Shomisha\Stubless\DeclarativeCode\ClassMethod[] */ 53 | public function getMethods(): array 54 | { 55 | return $this->methods; 56 | } 57 | 58 | protected function addMethodsToDeclaration(Declaration $declaration): void 59 | { 60 | foreach ($this->methods as $method) { 61 | $declaration->addStmts($method->getPrintableNodes()); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Concerns/HasAccessModifier.php: -------------------------------------------------------------------------------- 1 | access = $access; 15 | 16 | return $this; 17 | } 18 | 19 | public function getAccess(): ClassAccess 20 | { 21 | return $this->access; 22 | } 23 | 24 | public function makePublic(): self 25 | { 26 | $this->access = ClassAccess::PUBLIC(); 27 | 28 | return $this; 29 | } 30 | 31 | public function isPublic(): bool 32 | { 33 | return $this->access == ClassAccess::PUBLIC(); 34 | } 35 | 36 | public function makeProtected(): self 37 | { 38 | $this->access = ClassAccess::PROTECTED(); 39 | 40 | return $this; 41 | } 42 | 43 | public function isProtected(): bool 44 | { 45 | return $this->access == ClassAccess::PROTECTED(); 46 | } 47 | 48 | public function makePrivate(): self 49 | { 50 | $this->access = ClassAccess::PRIVATE(); 51 | 52 | return $this; 53 | } 54 | 55 | public function isPrivate(): bool 56 | { 57 | return $this->access == ClassAccess::PRIVATE(); 58 | } 59 | 60 | /** @param \PhpParser\Builder\Method|\PhpParser\Builder\Property $builder */ 61 | private function setAccessToBuilder(Builder $builder): void 62 | { 63 | switch ($this->access) { 64 | case ClassAccess::PUBLIC(): 65 | $builder->makePublic(); 66 | break; 67 | case ClassAccess::PROTECTED(): 68 | $builder->makeProtected(); 69 | break; 70 | case ClassAccess::PRIVATE(): 71 | $builder->makePrivate(); 72 | break; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/Concerns/HasArguments.php: -------------------------------------------------------------------------------- 1 | getArguments(); 19 | } 20 | 21 | return $this->withArguments($arguments); 22 | } 23 | 24 | public function addArgument(Argument $argument): self 25 | { 26 | $this->arguments[$argument->getName()] = $argument; 27 | 28 | return $this; 29 | } 30 | 31 | public function removeArgument(string $name): self 32 | { 33 | unset($this->arguments[$name]); 34 | 35 | return $this; 36 | } 37 | 38 | /** @param \Shomisha\Stubless\DeclarativeCode\Argument[] */ 39 | public function withArguments(array $arguments): self 40 | { 41 | $this->validateArrayElements($arguments, Argument::class); 42 | 43 | $this->arguments = []; 44 | 45 | foreach ($arguments as $argument) { 46 | $this->addArgument($argument); 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | /** @return \Shomisha\Stubless\DeclarativeCode\Argument[] */ 53 | public function getArguments(): array 54 | { 55 | return $this->arguments; 56 | } 57 | 58 | protected function addArgumentsToFunctionLike(FunctionLike $functionLike): void 59 | { 60 | foreach ($this->arguments as $argument) { 61 | $functionLike->addParam($argument->getPrintableNodes()[0]); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Values/AssociativeArrayValue.php: -------------------------------------------------------------------------------- 1 | keys, $this->elements] = $this->prepareKeysAndElements($keys, $elements); 16 | } 17 | 18 | public function add(AssignableValue $key, AssignableValue $value) 19 | { 20 | $this->keys[] = $key; 21 | $this->elements[] = $value; 22 | } 23 | 24 | protected function getPrintableRaw() 25 | { 26 | $elements = []; 27 | 28 | foreach ($this->keys as $offset => $key) { 29 | $elements[] = new ArrayItem( 30 | $this->elements[$offset]->getPrintableNodes()[0], 31 | $key->getPrintableNodes()[0] 32 | ); 33 | } 34 | 35 | return new Array_($elements); 36 | } 37 | 38 | private function prepareKeysAndElements(array $keys, array $elements): array 39 | { 40 | $totalKeys = count($keys); 41 | $totalElements = count($elements); 42 | 43 | if ($totalKeys > $totalElements) { 44 | $elements = array_pad($elements, $totalKeys, null); 45 | } 46 | 47 | if ($totalElements > $totalKeys) { 48 | $elements = array_slice($elements, 0, $totalKeys); 49 | } 50 | 51 | return [ 52 | array_map(fn($key) => Value::normalize($key), $keys), 53 | array_map(fn($element) => Value::normalize($element), $elements) 54 | ]; 55 | } 56 | 57 | protected function getImportSubDelegates(): array 58 | { 59 | return array_merge( 60 | $this->extractImportDelegatesFromArray($this->keys), 61 | $this->extractImportDelegatesFromArray($this->elements) 62 | ); 63 | } 64 | } -------------------------------------------------------------------------------- /tests/Unit/Declarative/UseStatementTest.php: -------------------------------------------------------------------------------- 1 | setAs('ChallengeModel'); 14 | 15 | 16 | $printed = $import->print(); 17 | 18 | 19 | $this->assertStringContainsString('use App\Models\Challenge as ChallengeModel;', $printed); 20 | } 21 | 22 | /** @test */ 23 | public function user_can_create_use_statement_without_alias() 24 | { 25 | $import = UseStatement::name(\App\Models\Point::class); 26 | 27 | 28 | $printed = $import->print(); 29 | 30 | 31 | $this->assertStringContainsString('use App\Models\Point;', $printed); 32 | } 33 | 34 | /** @test */ 35 | public function user_can_get_import_name() 36 | { 37 | $import = UseStatement::name(\App\Models\Car::class); 38 | 39 | 40 | $name = $import->getName(); 41 | 42 | 43 | $this->assertEquals('App\Models\Car', $name); 44 | } 45 | 46 | /** @test */ 47 | public function user_can_set_import_name() 48 | { 49 | $import = UseStatement::name(\Test\SomeNamespace\Old::class); 50 | 51 | 52 | $import->setName(\Test\SomeNamespace\Neue::class); 53 | $printed = $import->print(); 54 | 55 | 56 | $this->assertStringContainsString('use Test\SomeNamespace\Neue;', $printed); 57 | } 58 | 59 | /** @test */ 60 | public function user_can_get_import_alias() 61 | { 62 | $import = UseStatement::name(\App\Models\Bicycle::class)->setAs('Bike'); 63 | 64 | 65 | $alias = $import->getAs(); 66 | 67 | 68 | $this->assertEquals('Bike', $alias); 69 | } 70 | } -------------------------------------------------------------------------------- /src/Concerns/HasProperties.php: -------------------------------------------------------------------------------- 1 | getProperties(); 19 | } 20 | 21 | return $this->withProperties($properties); 22 | } 23 | 24 | public function addProperty(ClassProperty $property): self 25 | { 26 | $this->properties[$property->getName()] = $property; 27 | 28 | return $this; 29 | } 30 | 31 | public function removeProperty(string $name): self 32 | { 33 | unset($this->properties[$name]); 34 | 35 | return $this; 36 | } 37 | 38 | /** @param \Shomisha\Stubless\DeclarativeCode\ClassProperty[] $properties */ 39 | public function withProperties(array $properties): self 40 | { 41 | $this->validateArrayElements($properties, ClassProperty::class); 42 | 43 | $this->properties = []; 44 | 45 | foreach ($properties as $property) { 46 | $this->addProperty($property); 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | /** @return \Shomisha\Stubless\DeclarativeCode\ClassProperty[] */ 53 | public function getProperties(): array 54 | { 55 | return $this->properties; 56 | } 57 | 58 | protected function addPropertiesToDeclaration(Declaration $declaration): void 59 | { 60 | foreach ($this->properties as $property) { 61 | $declaration->addStmts($property->getPrintableNodes()); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Values/Closure.php: -------------------------------------------------------------------------------- 1 | arguments = $arguments; 25 | $this->body = $body; 26 | } 27 | 28 | public function uses(Variable $variable): self 29 | { 30 | $this->uses[] = $variable; 31 | 32 | return $this; 33 | } 34 | 35 | public function getPrintableNodes(): array 36 | { 37 | $arguments = array_map(function (Argument $argument) { 38 | return $argument->getPrintableNodes()[0]; 39 | }, $this->arguments); 40 | 41 | $uses = array_map(function (Variable $variable) { 42 | return $variable->getPrintableNodes()[0]; 43 | }, $this->uses); 44 | 45 | $body = []; 46 | if ($this->body !== null) { 47 | $body = $this->normalizeNodesToExpressions($this->body->getPrintableNodes()); 48 | } 49 | 50 | return [ 51 | new PhpParserClosure([ 52 | 'params' => $arguments, 53 | 'uses' => $uses, 54 | 'stmts' => $body, 55 | ]), 56 | ]; 57 | } 58 | 59 | protected function getImportSubDelegates(): array 60 | { 61 | return $this->extractImportDelegatesFromArray([ 62 | ...$this->arguments, 63 | $this->body 64 | ]); 65 | } 66 | } -------------------------------------------------------------------------------- /src/ImperativeCode/ControlBlocks/ForeachBlock.php: -------------------------------------------------------------------------------- 1 | arrayable = $arrayable; 23 | $this->valueContainer = $valueContainer; 24 | $this->body = $body; 25 | } 26 | 27 | public function withKey(?Variable $keyVariable): self 28 | { 29 | $this->keyContainer = $keyVariable; 30 | 31 | return $this; 32 | } 33 | 34 | public function do(?ImperativeCode $body): self 35 | { 36 | $this->body = $body; 37 | 38 | return $this; 39 | } 40 | 41 | public function getPrintableNodes(): array 42 | { 43 | $subNodes = []; 44 | 45 | if ($this->keyContainer !== null) { 46 | $subNodes['keyVar'] = $this->keyContainer->getAssignableValueExpression(); 47 | } 48 | 49 | if ($this->body !== null) { 50 | $subNodes['stmts'] = $this->normalizeNodesToExpressions($this->body->getPrintableNodes()); 51 | } 52 | 53 | return [ 54 | new Foreach_( 55 | $this->arrayable->getPrintableArrayExpr(), 56 | $this->valueContainer->getAssignableValueExpression(), 57 | $subNodes, 58 | ), 59 | ]; 60 | } 61 | 62 | protected function getImportSubDelegates(): array 63 | { 64 | return $this->extractImportDelegatesFromArray([ 65 | $this->arrayable, 66 | $this->body, 67 | ]); 68 | } 69 | } -------------------------------------------------------------------------------- /src/Formatters/CsFixerFormatter.php: -------------------------------------------------------------------------------- 1 | true, 13 | 'no_leading_import_slash' => true, 14 | 'ordered_imports' => true, 15 | 'single_line_after_imports' => true, 16 | 'blank_line_before_statement' => true, 17 | 'array_indentation' => true, 18 | 'method_chaining_indentation' => true, 19 | 'align_multiline_comment' => ['comment_type' => 'all_multiline'], 20 | 'no_empty_phpdoc' => true, 21 | 'phpdoc_indent' => true, 22 | 'array_syntax' => ['syntax' => 'short'], 23 | ]; 24 | 25 | public function format(string $code): string 26 | { 27 | $tempPath = $this->storeCodeAsTemp($code); 28 | 29 | $this->runFixer($tempPath); 30 | 31 | $formattedCode = $this->getCodeFromTemp($tempPath); 32 | 33 | $this->removeTemp($tempPath); 34 | 35 | return $formattedCode; 36 | } 37 | 38 | private function storeCodeAsTemp(string $code): string 39 | { 40 | $tempFile = tempnam(sys_get_temp_dir(), 'format_'); 41 | 42 | file_put_contents($tempFile, $code); 43 | 44 | return $tempFile; 45 | } 46 | 47 | private function runFixer(string $tempPath): void 48 | { 49 | exec($this->prepareFixerCommand($tempPath) . " 2>&1"); 50 | } 51 | 52 | private function prepareFixerCommand(string $path): string 53 | { 54 | $rules = json_encode($this->rules); 55 | 56 | return "\"{$this->csFixerPath}\" fix \"{$path}\" --rules='{$rules}' --using-cache=no"; 57 | } 58 | 59 | private function removeTemp(string $tempPath): void 60 | { 61 | unlink($tempPath); 62 | } 63 | 64 | private function getCodeFromTemp(string $tempPath): string 65 | { 66 | return file_get_contents($tempPath); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Abstractions/Code.php: -------------------------------------------------------------------------------- 1 | print()); 24 | } 25 | 26 | abstract public function print(): string; 27 | 28 | /** @return \PhpParser\Node[] */ 29 | abstract public function getPrintableNodes(): array; 30 | 31 | protected function getFactory(): BuilderFactory 32 | { 33 | if (!isset($this->factory)) { 34 | $this->factory = new BuilderFactory(); 35 | } 36 | 37 | return $this->factory; 38 | } 39 | 40 | protected function getPrinter(): PrettyPrinter 41 | { 42 | if (!isset($this->printer)) { 43 | $this->printer = new PrettyPrinter(); 44 | } 45 | 46 | return $this->printer; 47 | } 48 | 49 | protected function getFormatter(): Formatter 50 | { 51 | if (!isset($this->formatter)) { 52 | $this->formatter = new CsFixerFormatter(); 53 | } 54 | 55 | return $this->formatter; 56 | } 57 | 58 | protected function convertBuilderToNode(Builder $builder): Node 59 | { 60 | return $builder->getNode(); 61 | } 62 | 63 | protected function validateArrayElements(array $array, string $expectedClass): void 64 | { 65 | foreach ($array as $element) { 66 | if (! $element instanceof $expectedClass) { 67 | throw new \InvalidArgumentException("Array contains values that are not an instance of the '{$expectedClass}' class."); 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/DeclarativeCode/Argument.php: -------------------------------------------------------------------------------- 1 | name = $name; 21 | $this->type = $type; 22 | } 23 | 24 | public function type($type = null) 25 | { 26 | if ($type === null) { 27 | return $this->getType(); 28 | } 29 | 30 | return $this->setType($type); 31 | } 32 | 33 | public function getType(): ?string 34 | { 35 | return $this->type; 36 | } 37 | 38 | /** @param string|\Shomisha\Stubless\Utilities\Importable $type */ 39 | public function setType($type): self 40 | { 41 | if ($this->isImportable($type)) { 42 | $this->type = $type->getShortName(); 43 | $this->addImportable($type); 44 | } else { 45 | $this->type = $type; 46 | } 47 | 48 | return $this; 49 | } 50 | 51 | /** @return \PhpParser\Node\Param */ 52 | public function getPrintableNodes(): array 53 | { 54 | $argument = $this->getFactory()->param($this->name); 55 | 56 | if ($this->type !== null) { 57 | $argument->setType($this->type); 58 | } 59 | 60 | if ($value = $this->getValueExpr()) { 61 | $argument->setDefault($value); 62 | } 63 | 64 | return [$this->convertBuilderToNode($argument)]; 65 | } 66 | 67 | public function getImportSubDelegates(): array 68 | { 69 | return $this->extractImportDelegatesFromArray([ 70 | $this->value 71 | ]); 72 | } 73 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/ThrowBlockTest.php: -------------------------------------------------------------------------------- 1 | print(); 27 | 28 | 29 | $this->assertStringContainsString('throw $exceptionFactory->someImportantException;', $printed); 30 | } 31 | 32 | /** @test */ 33 | public function user_can_create_throw_block_using_factory_method() 34 | { 35 | $throw = Block::throw(Block::instantiate( 36 | new Importable('App\Exceptions\UserNotFoundException'), 37 | ['User with provided ID not present in database.'] 38 | )); 39 | 40 | 41 | $printed = $throw->print(); 42 | 43 | 44 | $this->assertStringContainsString('use App\Exceptions\UserNotFoundException;', $printed); 45 | $this->assertStringContainsString("throw new UserNotFoundException('User with provided ID not present in database.');", $printed); 46 | } 47 | 48 | /** 49 | * @test 50 | * @dataProvider objectContainersDataProvider 51 | */ 52 | public function user_can_create_throw_block_using_any_object_container(ObjectContainer $exception, string $printedException) 53 | { 54 | $printedException = str_replace('(new SomeClass())', 'new SomeClass()', $printedException); 55 | $throw = Block::throw($exception); 56 | 57 | 58 | $printed = $throw->print(); 59 | 60 | 61 | $this->assertStringContainsString("throw {$printedException};", $printed); 62 | } 63 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/InstantiateTest.php: -------------------------------------------------------------------------------- 1 | print(); 19 | 20 | 21 | $this->assertStringContainsString('new App\Models\User()', $printed); 22 | } 23 | 24 | /** @test */ 25 | public function user_can_create_instantiate_block_using_importable() 26 | { 27 | $instantiate = new InstantiateBlock(new Importable('App\Models\User')); 28 | 29 | 30 | $printed = $instantiate->print(); 31 | 32 | 33 | $this->assertStringContainsString('use App\Models\User;', $printed); 34 | $this->assertStringContainsString('new User()', $printed); 35 | 36 | $imports = $instantiate->getDelegatedImports(); 37 | $this->assertCount(1, $imports); 38 | $this->assertEquals('App\Models\User', $imports['App\Models\User']->getName()); 39 | } 40 | 41 | /** @test */ 42 | public function user_can_add_arguments_to_instantiation() 43 | { 44 | $instantiate = new InstantiateBlock('App\Models\User', [ 45 | [ 46 | 'first_name' => 'Misa', 47 | 'last_name' => 'Kovic', 48 | 'nickname' => 'Shomisha', 49 | ] 50 | ]); 51 | 52 | 53 | $printed = $instantiate->print(); 54 | 55 | 56 | $this->assertStringContainsString("new App\Models\User(['first_name' => 'Misa', 'last_name' => 'Kovic', 'nickname' => 'Shomisha'])", $printed); 57 | } 58 | 59 | /** @test */ 60 | public function user_can_create_instantiate_block_using_block_factory() 61 | { 62 | $instantiate = Block::instantiate('App\Models\User', [1]); 63 | 64 | 65 | $printed = $instantiate->print(); 66 | 67 | 68 | $this->assertStringContainsString('new App\Models\User(1);', $printed); 69 | } 70 | } -------------------------------------------------------------------------------- /src/References/ArrayKeyReference.php: -------------------------------------------------------------------------------- 1 | array = $array; 22 | $this->keys = [$key]; 23 | } 24 | 25 | public function nest(...$keys) 26 | { 27 | $this->keys = array_merge( 28 | $this->keys, 29 | array_map(function ($key) { 30 | return AssignableValue::normalize($key); 31 | }, $keys) 32 | ); 33 | 34 | return $this; 35 | } 36 | 37 | public function getPrintableNodes(): array 38 | { 39 | $keys = $this->keys; 40 | 41 | $firstKey = array_shift($keys); 42 | $arrayAccessExpr = new ArrayDimFetch($this->array->getPrintableArrayExpr(), $firstKey->getAssignableValueExpression()); 43 | 44 | return [ 45 | $this->nestArrayKeys($arrayAccessExpr, $keys), 46 | ]; 47 | } 48 | 49 | public function getAssignableContainerExpression(): Expr 50 | { 51 | return $this->getPrintableNodes()[0]; 52 | } 53 | 54 | public function getObjectContainerExpression(): Expr 55 | { 56 | return $this->getPrintableNodes()[0]; 57 | } 58 | 59 | protected function getImportSubDelegates(): array 60 | { 61 | return $this->extractImportDelegatesFromArray([ 62 | $this->array, 63 | ...$this->keys, 64 | ]); 65 | } 66 | 67 | private function nestArrayKeys(ArrayDimFetch $root, array $keys): ArrayDimFetch 68 | { 69 | if ($newKey = array_shift($keys)) { 70 | $root = $this->nestArrayKeys(new ArrayDimFetch($root, $newKey->getAssignableValueExpression()), $keys); 71 | } 72 | 73 | return $root; 74 | } 75 | } -------------------------------------------------------------------------------- /tests/Unit/Enums/ClassAccessTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(ClassAccess::class, $access); 17 | } 18 | 19 | /** @test */ 20 | public function all_class_accesses_can_be_returned_at_once() 21 | { 22 | $classAccesses = ClassAccess::all(); 23 | 24 | 25 | $this->assertEquals(ClassAccess::PUBLIC(), $classAccesses[0]); 26 | $this->assertEquals(ClassAccess::PROTECTED(), $classAccesses[1]); 27 | $this->assertEquals(ClassAccess::PRIVATE(), $classAccesses[2]); 28 | } 29 | 30 | /** @test */ 31 | public function class_access_can_be_cast_to_string() 32 | { 33 | $access = ClassAccess::PROTECTED(); 34 | 35 | 36 | $string = (string) $access; 37 | 38 | 39 | $this->assertEquals('protected', $string); 40 | } 41 | 42 | public function classAccessStringDataProvider() { 43 | return [ 44 | 'public' => ['public', ClassAccess::PUBLIC()], 45 | 'protected' => ['protected', ClassAccess::PROTECTED()], 46 | 'private' => ['private', ClassAccess::PRIVATE()], 47 | ]; 48 | } 49 | 50 | /** 51 | * @test 52 | * @dataProvider classAccessStringDataProvider 53 | */ 54 | public function class_access_can_be_created_from_string($string, $expectedAccess) 55 | { 56 | $actualAccess = ClassAccess::fromString($string); 57 | 58 | 59 | $this->assertEquals($actualAccess, $expectedAccess); 60 | } 61 | 62 | /** @test */ 63 | public function class_access_cannot_be_created_from_invalid_string() 64 | { 65 | $string = 'semi-public'; 66 | 67 | 68 | $this->expectException(\InvalidArgumentException::class); 69 | 70 | 71 | ClassAccess::fromString($string); 72 | } 73 | 74 | /** @test */ 75 | public function class_access_can_return_its_name() 76 | { 77 | $access = ClassAccess::PRIVATE(); 78 | 79 | 80 | $name = $access->value(); 81 | 82 | 83 | $this->assertEquals('private', $name); 84 | } 85 | } -------------------------------------------------------------------------------- /src/Concerns/HasImports.php: -------------------------------------------------------------------------------- 1 | getImports(); 19 | } 20 | 21 | return $this->withImports($imports); 22 | } 23 | 24 | public function addImport(UseStatement $import): self 25 | { 26 | $this->imports[$import->getName()] = $import; 27 | 28 | return $this; 29 | } 30 | 31 | public function removeImport(string $name): self 32 | { 33 | unset($this->imports[$name]); 34 | 35 | return $this; 36 | } 37 | 38 | /** @param \Shomisha\Stubless\ImperativeCode\UseStatement[] $imports */ 39 | public function withImports(array $imports): self 40 | { 41 | $this->validateArrayElements($imports, UseStatement::class); 42 | 43 | $this->imports = []; 44 | 45 | foreach ($imports as $import) { 46 | $this->addImport($import); 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | /** @return \Shomisha\Stubless\ImperativeCode\UseStatement[] */ 53 | public function getImports(): array 54 | { 55 | return $this->imports; 56 | } 57 | 58 | public function addImportable(Importable $importable): self 59 | { 60 | $this->imports[$importable->getFullName()] = $importable->getImportStatement(); 61 | 62 | return $this; 63 | } 64 | 65 | protected function isImportable($value): bool 66 | { 67 | return $value instanceof Importable; 68 | } 69 | 70 | /** 71 | * @param \Shomisha\Stubless\Contracts\DelegatesImports[] $delegates 72 | * @return \Shomisha\Stubless\ImperativeCode\UseStatement[] 73 | */ 74 | protected function gatherImportsFromDelegates(array $delegates): array 75 | { 76 | $imports = []; 77 | 78 | foreach ($delegates as $delegate) { 79 | $imports = array_merge($imports, $delegate->getDelegatedImports()); 80 | } 81 | 82 | return $imports; 83 | } 84 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/ReturnTest.php: -------------------------------------------------------------------------------- 1 | print(); 25 | 26 | 27 | $this->assertStringContainsString('return $return', $printed); 28 | } 29 | 30 | public function valueDataProvider() 31 | { 32 | return [ 33 | [Value::string('help me'), "return 'help me'"], 34 | [Value::integer(15), 'return 15'], 35 | [Value::float(3.14), 'return 3.14'], 36 | [Value::array([1, 2, 3]), 'return [1, 2, 3]'], 37 | [Value::boolean(true), 'return true'], 38 | [Value::null(), 'return null'], 39 | ]; 40 | } 41 | 42 | /** 43 | * @test 44 | * @dataProvider valueDataProvider 45 | */ 46 | public function user_can_add_prime_values_to_return_block($value, $expectedPrint) 47 | { 48 | $return = new ReturnBlock($value); 49 | 50 | 51 | $printed = $return->print(); 52 | 53 | 54 | $this->assertStringContainsString($expectedPrint, $printed); 55 | } 56 | 57 | /** @test */ 58 | public function user_can_create_return_block_using_block_factory() 59 | { 60 | $return = Block::return( 61 | Block::invokeStaticMethod( 62 | Reference::classReference(new Importable('App\Models\User')), 63 | 'finalize' 64 | ) 65 | ); 66 | 67 | 68 | $printed = $return->print(); 69 | 70 | 71 | $this->assertStringContainsString('return User::finalize()', $printed); 72 | 73 | $imports = $return->getDelegatedImports(); 74 | $this->assertCount(1, $imports); 75 | } 76 | 77 | /** 78 | * @test 79 | * @dataProvider assignableValuesDataProvider 80 | */ 81 | public function user_can_add_any_assignable_values_to_return_block($value, string $printedValue) 82 | { 83 | $return = Block::return($value); 84 | 85 | 86 | $printed = $return->print(); 87 | 88 | 89 | $this->assertStringContainsString("return {$printedValue};", $printed); 90 | } 91 | } -------------------------------------------------------------------------------- /src/DeclarativeCode/ClassProperty.php: -------------------------------------------------------------------------------- 1 | name = $name; 32 | $this->type = $type; 33 | $this->value = $value; 34 | $this->access = $access ?? ClassAccess::PUBLIC(); 35 | } 36 | 37 | public function type($type = null) 38 | { 39 | if ($type === null) { 40 | return $this->getType(); 41 | } 42 | 43 | return $this->setType($type); 44 | } 45 | 46 | public function getType(): string 47 | { 48 | return $this->type; 49 | } 50 | 51 | /** @param string|\Shomisha\Stubless\Utilities\Importable $type */ 52 | public function setType($type): self 53 | { 54 | $this->type = (string) $type; 55 | 56 | if ($this->isImportable($type)) { 57 | $this->addImportable($type); 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | public function withDefaultDocBlock(): self 64 | { 65 | $docBlock = "@var"; 66 | 67 | if ($type = $this->type) { 68 | $docBlock .= " {$type}"; 69 | } 70 | 71 | $docBlock .= " \${$this->name}"; 72 | 73 | return $this->withDocBlock($docBlock); 74 | } 75 | 76 | public function getPrintableNodes(): array 77 | { 78 | $property = $this->getFactory()->property($this->name); 79 | 80 | $this->setAccessToBuilder($property); 81 | $this->makeBuilderStatic($property); 82 | 83 | if ($this->type !== null) { 84 | $property->setType($this->type); 85 | } 86 | 87 | if ($value = $this->getValueExpr()) { 88 | $property->setDefault($value); 89 | } 90 | 91 | $this->setDocBlockCommentOnBuilder($property); 92 | 93 | return [$this->convertBuilderToNode($property)]; 94 | } 95 | 96 | public function getImportSubDelegates(): array 97 | { 98 | return $this->extractImportDelegatesFromArray([ 99 | $this->value 100 | ]); 101 | } 102 | } -------------------------------------------------------------------------------- /src/ImperativeCode/ControlBlocks/IfBlock.php: -------------------------------------------------------------------------------- 1 | condition = $condition; 27 | $this->body = $body; 28 | } 29 | 30 | public function then(?ImperativeCode $body): self 31 | { 32 | $this->body = $body; 33 | 34 | return $this; 35 | } 36 | 37 | public function elseif($condition, ?ImperativeCode $body): self 38 | { 39 | $this->elseIfs[] = [ 40 | self::ELSEIF_KEY_CONDITION => AssignableValue::normalize($condition), 41 | self::ELSEIF_KEY_BODY => $body 42 | ]; 43 | 44 | return $this; 45 | } 46 | 47 | public function else(?ImperativeCode $body): self 48 | { 49 | $this->elseBlock = $body; 50 | 51 | return $this; 52 | } 53 | 54 | public function getPrintableNodes(): array 55 | { 56 | $elseIfs = array_map(function (array $elseIfConditionAndBody) { 57 | /** @var AssignableValue $condition */ 58 | $condition = $elseIfConditionAndBody[self::ELSEIF_KEY_CONDITION]; 59 | 60 | /** @var \Shomisha\Stubless\ImperativeCode\Block|null $block */ 61 | $block = $elseIfConditionAndBody[self::ELSEIF_KEY_BODY]; 62 | 63 | return new ElseIf_( 64 | $condition->getAssignableValueExpression(), 65 | $this->normalizeNodesToExpressions($block->getPrintableNodes()) 66 | ); 67 | }, $this->elseIfs); 68 | 69 | $body = []; 70 | if ($this->body) { 71 | $body = $this->normalizeNodesToExpressions($this->body->getPrintableNodes()); 72 | } 73 | 74 | $else = null; 75 | if ($this->elseBlock) { 76 | $else = new Else_( 77 | $this->normalizeNodesToExpressions($this->elseBlock->getPrintableNodes()) 78 | ); 79 | } 80 | 81 | return [ 82 | new If_( 83 | $this->condition->getAssignableValueExpression(), 84 | [ 85 | 'stmts' => $body, 86 | 'elseifs' => $elseIfs, 87 | 'else' => $else, 88 | ] 89 | ) 90 | ]; 91 | } 92 | 93 | protected function getImportSubDelegates(): array 94 | { 95 | $allElements = [ 96 | $this->condition, 97 | $this->body, 98 | $this->elseBlock, 99 | ]; 100 | 101 | foreach ($this->elseIfs as $conditionAndBody) { 102 | $allElements[] = $conditionAndBody[self::ELSEIF_KEY_CONDITION]; 103 | $allElements[] = $conditionAndBody[self::ELSEIF_KEY_BODY]; 104 | } 105 | 106 | return $this->extractImportDelegatesFromArray($allElements); 107 | } 108 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stubless 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/shomisha/stubless)](https://packagist.org/packages/shomisha/stubless) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE) 5 | 6 | Stubless is a package for generating PHP source code using a readable, fluent, object-oriented API, and without the usage of any stubs. 7 | It is ideal for generator commands and takes away the need for manipulating strings when programmatically generating PHP code. 8 | 9 | Here is an example of what Stubless can do: 10 | 11 | ```php 12 | extends(new Importable(Controller::class))->setNamespace('App\Http\Controllers'); 26 | 27 | $requestArgument = Argument::name('request')->type(new Importable(Request::class)); 28 | $userArgument = Argument::name('user')->type(new Importable(User::class)); 29 | $userRequestArgument = Argument::name('request')->type(new Importable(UserRequest::class)); 30 | 31 | $class->withMethods([ 32 | ClassMethod::name('index')->addArgument($requestArgument), 33 | ClassMethod::name('show')->withArguments([ 34 | $requestArgument, 35 | $userArgument, 36 | ]), 37 | ClassMethod::name('store')->addArgument($userRequestArgument), 38 | ClassMethod::name('update')->withArguments([ 39 | $userRequestArgument, 40 | $userArgument, 41 | ]), 42 | ClassMethod::name('destroy')->addArgument($userArgument), 43 | ]); 44 | 45 | echo $class->print(); 46 | ``` 47 | 48 | The following code would generate this PHP source code: 49 | 50 | ```php 51 | print(); 28 | 29 | 30 | $this->assertStringContainsString("\$user->first_name = \$request->input('first_name')", $printed); 31 | } 32 | 33 | /** @test */ 34 | public function user_can_create_assign_block_using_block_factory() 35 | { 36 | $assign = Block::assign( 37 | Variable::name('userAge'), 38 | Reference::objectProperty(Variable::name('user'), 'age') 39 | ); 40 | 41 | 42 | $printed = $assign->print(); 43 | 44 | 45 | $this->assertStringContainsString('$userAge = $user->age', $printed); 46 | } 47 | 48 | /** @test */ 49 | public function user_can_pass_raw_values_when_creating_block_using_factory() 50 | { 51 | $assign = Block::assign( 52 | 'test', 53 | false 54 | ); 55 | 56 | 57 | $printed = $assign->print(); 58 | 59 | 60 | $this->assertStringContainsString('$test = false', $printed); 61 | } 62 | 63 | /** 64 | * @test 65 | * @dataProvider assignableContainersDataProvider 66 | */ 67 | public function user_can_create_assign_blocks_with_any_assignable_container(AssignableContainer $container, string $printedContainer) 68 | { 69 | $assign = Block::assign($container, 'test'); 70 | 71 | 72 | $printed = $assign->print(); 73 | 74 | 75 | $this->assertStringContainsString("{$printedContainer} = 'test';", $printed); 76 | } 77 | 78 | /** 79 | * @test 80 | * @dataProvider assignableValuesDataProvider 81 | */ 82 | public function user_can_create_assign_blocks_with_any_assignable_value($value, string $printedValue) 83 | { 84 | $assignBlock = Block::assign('test', $value); 85 | 86 | 87 | $printed = $assignBlock->print(); 88 | 89 | 90 | $this->assertStringContainsString("\$test = {$printedValue};", $printed); 91 | } 92 | 93 | public function invalidAssignBlockArgumentDataProvider() 94 | { 95 | return [ 96 | [15], 97 | [false], 98 | [[1, 2, 3]], 99 | [ClassMethod::name('test')], 100 | [Block::return(15)], 101 | ]; 102 | } 103 | 104 | /** 105 | * @test 106 | * @dataProvider invalidAssignBlockArgumentDataProvider 107 | */ 108 | public function user_cannot_pass_invalid_value_to_create_assign_block_when_using_factory($variable) 109 | { 110 | $this->expectException(\InvalidArgumentException::class); 111 | 112 | Block::assign($variable, 'test'); 113 | } 114 | } -------------------------------------------------------------------------------- /src/Comparisons/Comparison.php: -------------------------------------------------------------------------------- 1 | first = $first; 20 | $this->second = $second; 21 | } 22 | 23 | public function or($second): OrComparison 24 | { 25 | return new OrComparison($this, $second); 26 | } 27 | 28 | public function and($second): AndComparison 29 | { 30 | return new AndComparison($this, AssignableValue::normalize($second)); 31 | } 32 | 33 | public function negate(bool $negate = true): self 34 | { 35 | $this->negated = $negate; 36 | 37 | return $this; 38 | } 39 | 40 | final public function getPrintableNodes(): array 41 | { 42 | $comparableNode = $this->getComparableNode(); 43 | 44 | if ($this->negated) { 45 | $comparableNode = new Node\Expr\BooleanNot($comparableNode); 46 | } 47 | 48 | return [ 49 | $comparableNode 50 | ]; 51 | } 52 | 53 | abstract protected function getComparableNode(): Node; 54 | 55 | protected function getImportSubDelegates(): array 56 | { 57 | return [ 58 | $this->first, 59 | $this->second 60 | ]; 61 | } 62 | 63 | public static function equals($first, $second): Equals 64 | { 65 | return new Equals(AssignableValue::normalize($first), AssignableValue::normalize($second)); 66 | } 67 | 68 | public static function equalsStrict($first, $second): EqualsStrict 69 | { 70 | return new EqualsStrict(AssignableValue::normalize($first), AssignableValue::normalize($second)); 71 | } 72 | 73 | public static function notEquals($first, $second): NotEquals 74 | { 75 | return new NotEquals(AssignableValue::normalize($first), AssignableValue::normalize($second)); 76 | } 77 | 78 | public static function notEqualsStrict($first, $second): NotEqualsStrict 79 | { 80 | return new NotEqualsStrict(AssignableValue::normalize($first), AssignableValue::normalize($second)); 81 | } 82 | 83 | public static function greaterThan($first, $second): GreaterThan 84 | { 85 | return new GreaterThan(AssignableValue::normalize($first), AssignableValue::normalize($second)); 86 | } 87 | 88 | public static function greaterThanEquals($first, $second): GreaterThanEquals 89 | { 90 | return new GreaterThanEquals(AssignableValue::normalize($first), AssignableValue::normalize($second)); 91 | } 92 | 93 | public static function lesserThan($first, $second): LesserThan 94 | { 95 | return new LesserThan(AssignableValue::normalize($first), AssignableValue::normalize($second)); 96 | } 97 | 98 | public static function lesserThanEquals($first, $second): LesserThanEquals 99 | { 100 | return new LesserThanEquals(AssignableValue::normalize($first), AssignableValue::normalize($second)); 101 | } 102 | 103 | public static function not($condition): Not 104 | { 105 | return new Not(AssignableValue::normalize($condition)); 106 | } 107 | } -------------------------------------------------------------------------------- /src/ImperativeCode/InvokeBlock.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | $this->arguments = array_map(function ($value) { 23 | return AssignableValue::normalize($value); 24 | }, $arguments); 25 | } 26 | 27 | public function chain(string $name = null, array $arguments = []) 28 | { 29 | if ($name !== null) { 30 | $chainedMethod = new ChainedMethodBlock($this, $name, $arguments); 31 | return $this->setChain($chainedMethod); 32 | } 33 | 34 | return $this->getChainedMethod(); 35 | } 36 | 37 | public function setChain(?ChainedMethodBlock $block): ?ChainedMethodBlock 38 | { 39 | $this->chainedMethod = $block; 40 | 41 | return $block; 42 | } 43 | 44 | public function continueChain(string $name, array $arguments = []): self 45 | { 46 | if ($this->chainedMethod !== null) { 47 | $this->chainedMethod->continueChain($name, $arguments); 48 | } else { 49 | $this->chain($name, $arguments); 50 | } 51 | 52 | return $this; 53 | } 54 | 55 | /** @return \Shomisha\Stubless\ImperativeCode\ChainedMethodBlock[] */ 56 | public function getChainedMethod(): ?ChainedMethodBlock 57 | { 58 | return $this->chainedMethod; 59 | } 60 | 61 | public function hasChainedMethod(): bool 62 | { 63 | return $this->chainedMethod !== null; 64 | } 65 | 66 | final public function getPrintableNodes(): array 67 | { 68 | if ($this->hasChainedMethod()) { 69 | return $this->getChainedMethod()->getPrintableNodes(); 70 | } 71 | 72 | return $this->getInvokablePrintableNodes(); 73 | } 74 | 75 | /** @return \PhpParser\Node[] */ 76 | protected abstract function getInvokablePrintableNodes(): array; 77 | 78 | /** @return \PhpParser\Node\Arg[] */ 79 | protected function normalizedArguments(): array 80 | { 81 | return array_map(function (AssignableValue $argument) { 82 | return new Arg($argument->getAssignableValueExpression()); 83 | }, $this->arguments); 84 | } 85 | 86 | public function getDelegatedImports(): array 87 | { 88 | $imports = $this->getImports(); 89 | 90 | if ($this->hasChainedMethod()) { 91 | $imports = array_merge($imports, $this->chainedMethod->getChainedImports()); 92 | } 93 | 94 | foreach ($this->getImportSubDelegates() as $subDelegate) { 95 | $imports = array_merge($imports, $subDelegate->getDelegatedImports()); 96 | } 97 | 98 | return $imports; 99 | } 100 | 101 | public function getImportSubDelegates(): array 102 | { 103 | return $this->extractImportDelegatesFromArray($this->arguments); 104 | } 105 | 106 | public function getPrintableArrayExpr(): Expr 107 | { 108 | return $this->getPrintableNodes()[0]; 109 | } 110 | 111 | public function getObjectContainerExpression(): Expr 112 | { 113 | return $this->getPrintableNodes()[0]; 114 | } 115 | } -------------------------------------------------------------------------------- /tests/Unit/ImportableTest.php: -------------------------------------------------------------------------------- 1 | getShortName(); 26 | 27 | 28 | $this->assertEquals($expectedShortName, $actualShortName); 29 | } 30 | 31 | /** @test */ 32 | public function importable_will_use_alias_instead_of_short_class_name_if_one_is_provided() 33 | { 34 | $importable = new Importable('App\Contracts\Taggable', 'TaggableContract'); 35 | 36 | 37 | $shortName = $importable->getShortName(); 38 | 39 | 40 | $this->assertEquals('TaggableContract', $shortName); 41 | } 42 | 43 | /** @test */ 44 | public function importable_can_prepare_import_statement_based_on_full_class_name_provided() 45 | { 46 | $importable = new Importable('App\Models\User'); 47 | 48 | 49 | $useStatement = $importable->getImportStatement(); 50 | 51 | 52 | $this->assertInstanceOf(UseStatement::class, $useStatement); 53 | $this->assertEquals('App\Models\User', $useStatement->getName()); 54 | } 55 | 56 | /** @test */ 57 | public function prepared_import_statement_will_contain_alias_if_one_is_provided() 58 | { 59 | $importable = new Importable('App\Models\User', 'UserModel'); 60 | 61 | 62 | $useStatement = $importable->getImportStatement(); 63 | 64 | 65 | $this->assertInstanceOf(UseStatement::class, $useStatement); 66 | $this->assertEquals('App\Models\User', $useStatement->getName()); 67 | $this->assertEquals('UserModel', $useStatement->getAs()); 68 | } 69 | 70 | /** @test */ 71 | public function importable_can_return_full_class_name() 72 | { 73 | $importable = new Importable('App\Http\Controllers\CustomersController'); 74 | 75 | 76 | $fullClassName = $importable->getFullName(); 77 | 78 | 79 | $this->assertEquals('App\Http\Controllers\CustomersController', $fullClassName); 80 | } 81 | 82 | /** @test */ 83 | public function importable_will_not_be_printed_more_than_once() 84 | { 85 | $userVar = Variable::name('user'); 86 | $block = Block::fromArray([ 87 | Block::assign( 88 | $userVar, 89 | Block::invokeStaticMethod( 90 | Reference::classReference(new Importable('App\Models\User')), 91 | 'first' 92 | ) 93 | ), 94 | Block::invokeMethod( 95 | $userVar, 96 | 'assignMentor', 97 | [ 98 | Block::invokeStaticMethod( 99 | Reference::classReference(new Importable('App\Models\User')), 100 | 'find', 101 | [15] 102 | ) 103 | ] 104 | ) 105 | ]); 106 | 107 | 108 | $printed = $block->print(); 109 | 110 | 111 | $this->assertEquals(1, substr_count($printed, 'use App\Models\User;')); 112 | } 113 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/ForeachBlockTest.php: -------------------------------------------------------------------------------- 1 | print(); 25 | 26 | 27 | $this->assertStringContainsString("foreach (\$object->arrayProperty as \$arrayElement) {\n}", $printed); 28 | } 29 | 30 | /** 31 | * @test 32 | * @dataProvider arrayablesDataProvider 33 | */ 34 | public function user_can_create_foreach_block_with_any_arrayable_implementation(Arrayable $arrayable, string $printedArrayable) 35 | { 36 | $foreach = Block::foreach($arrayable, Reference::variable('test')); 37 | 38 | 39 | $printed = $foreach->print(); 40 | 41 | 42 | $this->assertStringContainsString("foreach ({$printedArrayable} as \$test) {\n}", $printed); 43 | } 44 | 45 | /** @test */ 46 | public function user_can_create_foreach_block_using_direct_constructor() 47 | { 48 | $foreach = new ForeachBlock(Reference::variable('someArray'), Reference::variable('arrayElement'), Block::invokeFunction('doSomething')); 49 | 50 | 51 | $printed = $foreach->print(); 52 | 53 | 54 | $this->assertStringContainsString("foreach (\$someArray as \$arrayElement) {\n doSomething();\n}", $printed); 55 | } 56 | 57 | /** 58 | * @test 59 | * @dataProvider imperativeCodeDataProvider 60 | */ 61 | public function user_use_any_imperative_code_as_foreach_body(ImperativeCode $code, string $printedCode) 62 | { 63 | $foreach = Block::foreach(Reference::variable('someArray'), Reference::variable('arrayElement')); 64 | 65 | 66 | $foreach->do($code); 67 | $printed = $foreach->print(); 68 | 69 | 70 | $this->assertStringContainsString("foreach (\$someArray as \$arrayElement) {\n {$printedCode}\n}", $printed); 71 | } 72 | 73 | /** @test */ 74 | public function user_can_pass_variable_for_storing_key_in_foreach_block() 75 | { 76 | $foreach = Block::foreach(Reference::variable('someArray'), Reference::variable('value')); 77 | 78 | 79 | $foreach->withKey(Reference::variable('key')); 80 | $printed = $foreach->print(); 81 | 82 | 83 | $this->assertStringContainsString("foreach (\$someArray as \$key => \$value) {\n}", $printed); 84 | } 85 | 86 | /** @test */ 87 | public function foreach_will_delegate_imports_from_its_elements() 88 | { 89 | $foreach = Block::foreach(Reference::staticProperty(new Importable('App\Models\User'), 'allUsers'), Reference::variable('user')); 90 | $foreach->do( 91 | Block::invokeStaticMethod(new Importable('App\Models\Post'), 'deletePostsForUser', [Reference::variable('user')]) 92 | ); 93 | 94 | 95 | $printed = $foreach->print(); 96 | 97 | 98 | $this->assertStringContainsString('use App\Models\User;', $printed); 99 | $this->assertStringContainsString('use App\Models\Post;', $printed); 100 | 101 | $this->assertStringContainsString("foreach (User::\$allUsers as \$user) {\n Post::deletePostsForUser(\$user);\n}", $printed); 102 | } 103 | } -------------------------------------------------------------------------------- /src/DeclarativeCode/ClassMethod.php: -------------------------------------------------------------------------------- 1 | name = $name; 39 | $this->arguments = $arguments; 40 | $this->access = $access ?? ClassAccess::PUBLIC(); 41 | $this->returnType = $returnType; 42 | } 43 | 44 | public function body(ImperativeCode $body = null) 45 | { 46 | if ($body !== null) { 47 | return $this->setBody($body); 48 | } 49 | 50 | return $this->getBody(); 51 | } 52 | 53 | public function setBody(ImperativeCode $body): self 54 | { 55 | $this->body = $body; 56 | 57 | return $this; 58 | } 59 | 60 | public function getBody(): ?ImperativeCode 61 | { 62 | return $this->body; 63 | } 64 | 65 | public function hasBody(): bool 66 | { 67 | return isset($this->body); 68 | } 69 | 70 | public function return($returnType = null) 71 | { 72 | if ($returnType === null) { 73 | return $this->getReturnType(); 74 | } 75 | 76 | return $this->setReturnType($returnType); 77 | } 78 | 79 | public function getReturnType(): ?string 80 | { 81 | return $this->returnType; 82 | } 83 | 84 | public function setReturnType($returnType): self 85 | { 86 | $this->returnType = (string) $returnType; 87 | 88 | if ($this->isImportable($returnType)) { 89 | $this->addImportable($returnType); 90 | } 91 | 92 | return $this; 93 | } 94 | 95 | public function withDefaultDocBlock(): self 96 | { 97 | $docBlock = ''; 98 | 99 | /** @var \Shomisha\Stubless\DeclarativeCode\Argument $argument */ 100 | foreach ($this->arguments as $argument) { 101 | $argumentDoc = "@param"; 102 | 103 | if ($type = $argument->getType()) { 104 | $argumentDoc .= " {$type}"; 105 | } 106 | 107 | $argumentDoc .= " \${$argument->getName()}"; 108 | 109 | $docBlock .= "{$argumentDoc}\n"; 110 | } 111 | 112 | if ($returnType = $this->returnType) { 113 | $docBlock .= "@return {$returnType}"; 114 | } 115 | 116 | return $this->withDocBlock($docBlock); 117 | } 118 | 119 | /** @return \PhpParser\Node\Stmt\ClassMethod[] */ 120 | public function getPrintableNodes(): array 121 | { 122 | $method = $this->getFactory()->method($this->name); 123 | 124 | $this->makeBuilderAbstract($method); 125 | $this->makeBuilderFinal($method); 126 | $this->makeBuilderStatic($method); 127 | 128 | $this->setAccessToBuilder($method); 129 | 130 | $this->addArgumentsToFunctionLike($method); 131 | 132 | if ($this->hasBody()) { 133 | $method->addStmts($this->body->getPrintableNodes()); 134 | } 135 | 136 | if ($this->returnType !== null) { 137 | $method->setReturnType($this->returnType); 138 | } 139 | 140 | $this->setDocBlockCommentOnBuilder($method); 141 | 142 | return [$this->convertBuilderToNode($method)]; 143 | } 144 | 145 | public function getImportSubDelegates(): array 146 | { 147 | $subDelegates = [ 148 | ...array_values($this->arguments), 149 | ]; 150 | 151 | if ($this->hasBody()) { 152 | $subDelegates[] = $this->body; 153 | } 154 | 155 | return $subDelegates; 156 | } 157 | } -------------------------------------------------------------------------------- /tests/Unit/Declarative/ArgumentTest.php: -------------------------------------------------------------------------------- 1 | type('string'); 19 | 20 | 21 | $printed = $argument->print(); 22 | 23 | 24 | $this->assertStringContainsString('string $test', $printed); 25 | } 26 | 27 | /** @test */ 28 | public function users_can_create_argument_without_type() 29 | { 30 | $argument = Argument::name('test'); 31 | 32 | 33 | $printed = $argument->print(); 34 | 35 | 36 | $this->assertEquals("type('int'); 43 | 44 | 45 | $type = $argument->getType(); 46 | 47 | 48 | $this->assertEquals('int', $type); 49 | } 50 | 51 | /** @test */ 52 | public function user_can_get_argument_type_using_fluent_alias() 53 | { 54 | $argument = Argument::name('test')->type('array'); 55 | 56 | 57 | $type = $argument->type(); 58 | 59 | 60 | $this->assertEquals('array', $type); 61 | } 62 | 63 | /** @test */ 64 | public function users_can_add_default_values_to_arguments() 65 | { 66 | $argument = Argument::name('test')->type('string'); 67 | 68 | 69 | $argument->setValue('testValue'); 70 | $printed = $argument->print(); 71 | 72 | 73 | $this->assertStringContainsString("string \$test = 'testValue'", $printed); 74 | } 75 | 76 | /** @test */ 77 | public function users_cannot_use_objects_as_argument_default_values() 78 | { 79 | $argument = Argument::name('test'); 80 | 81 | 82 | $this->expectException(\InvalidArgumentException::class); 83 | 84 | 85 | $argument->setValue(new \stdClass()); 86 | } 87 | 88 | /** @test */ 89 | public function user_can_use_importables_as_argument_default_values() 90 | { 91 | $classTemplate = ClassTemplate::name('ParentClass')->withMethods([ 92 | ClassMethod::name('someMethod')->addArgument( 93 | Argument::name('someClassName')->value(new Importable('App\Services\SomeClass')) 94 | ) 95 | ]); 96 | 97 | 98 | $printed = $classTemplate->print(); 99 | 100 | 101 | $this->assertStringContainsString('use App\Services\SomeClass;', $printed); 102 | $this->assertStringContainsString('public function someMethod($someClassName = SomeClass::class)', $printed); 103 | } 104 | 105 | /** @test */ 106 | public function user_can_use_class_references_as_argument_default_values() 107 | { 108 | $argument = Argument::name('someArgument')->value(ClassReference::name('SomeClass')); 109 | 110 | 111 | $printed = $argument->print(); 112 | 113 | 114 | $this->assertStringContainsString('$someArgument = SomeClass::class', $printed); 115 | } 116 | 117 | /** @test */ 118 | public function users_can_add_default_value_to_arguments_using_the_fluent_alias() 119 | { 120 | $argument = Argument::name('test')->type('string'); 121 | 122 | 123 | $argument->value('testValue'); 124 | $printed = $argument->print(); 125 | 126 | 127 | $this->assertStringContainsString("string \$test = 'testValue'", $printed); 128 | } 129 | 130 | /** @test */ 131 | public function user_can_use_empty_array_as_argument_default_value() 132 | { 133 | $argument = Argument::name('override')->type('array')->value([]); 134 | 135 | 136 | $printed = Value::closure([$argument])->print(); 137 | 138 | 139 | $this->assertStringContainsString("function (array \$override = [])", $printed); 140 | } 141 | 142 | /** @test */ 143 | public function users_can_get_the_default_value_from_arguments() 144 | { 145 | $argument = Argument::name('test')->value(15); 146 | 147 | 148 | $value = $argument->getValue(); 149 | 150 | 151 | $this->assertEquals(15, $value); 152 | } 153 | 154 | /** @test */ 155 | public function users_can_get_the_default_value_from_arguments_using_the_fluent_alias() 156 | { 157 | $argument = Argument::name('test')->value([1, 2, 3]); 158 | 159 | 160 | $value = $argument->value(); 161 | 162 | 163 | $this->assertEquals([1, 2, 3], $value); 164 | } 165 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/BlockTest.php: -------------------------------------------------------------------------------- 1 | print(); 54 | 55 | 56 | $this->assertStringContainsString('use App\Models\Post;', $printed); 57 | $this->assertStringContainsString("\$post = Post::find(\$request->input('post_id'));", $printed); 58 | $this->assertStringContainsString("\$post->is_public = \$request->input('is_public');", $printed); 59 | $this->assertStringContainsString("\$post->update();", $printed); 60 | } 61 | 62 | /** @test */ 63 | public function user_can_create_block_using_direct_constructor() 64 | { 65 | $block = new Block([ 66 | new AssignBlock(Variable::name('test'), Value::integer(1)) 67 | ]); 68 | 69 | 70 | $printed = $block->print(); 71 | 72 | 73 | $this->assertStringContainsString('$test = 1;', $printed); 74 | } 75 | 76 | /** @test */ 77 | public function user_can_create_block_from_array_of_subblocks() 78 | { 79 | $block = Block::fromArray([ 80 | Block::assign( 81 | Variable::name('user'), 82 | Block::instantiate(new Importable('App\Models\User')) 83 | ), 84 | Block::invokeMethod( 85 | Variable::name('user'), 86 | 'activate', 87 | [true] 88 | ), 89 | ]); 90 | 91 | 92 | $printed = $block->print(); 93 | 94 | 95 | $this->assertStringContainsString('use App\Models\User;', $printed); 96 | $this->assertStringContainsString("\$user = new User();\n\$user->activate(true);", $printed); 97 | } 98 | 99 | /** @test */ 100 | public function user_can_add_subblock_to_block() 101 | { 102 | $block = new Block(); 103 | 104 | $block->addCode( 105 | new InvokeStaticMethodBlock( 106 | new ClassReference(new Importable('App\Models\User')), 107 | 'deleteAll' 108 | ) 109 | ); 110 | 111 | 112 | $printed = $block->print(); 113 | 114 | 115 | $this->assertStringContainsString('use App\Models\User;', $printed); 116 | $this->assertStringContainsString('User::deleteAll();', $printed); 117 | } 118 | 119 | /** @test */ 120 | public function user_can_add_multiple_subblocks_to_block() 121 | { 122 | $block = new Block(); 123 | 124 | $userVar = Variable::name('user'); 125 | 126 | $block->addCodes([ 127 | Block::assign( 128 | $userVar, 129 | Block::invokeStaticMethod( 130 | Reference::classReference((new Importable('App\Models\User'))), 131 | 'find', 132 | [1] 133 | ) 134 | ), 135 | Block::invokeMethod( 136 | $userVar, 137 | 'delete' 138 | ), 139 | ]); 140 | 141 | 142 | $printed = $block->print(); 143 | 144 | 145 | $this->assertStringContainsString('use App\Models\User;', $printed); 146 | $this->assertStringContainsString("\$user = User::find(1);\n\$user->delete();", $printed); 147 | } 148 | 149 | /** @test */ 150 | public function user_can_get_code_instances_from_block() 151 | { 152 | $expectedCode = [ 153 | Block::assign( 154 | Reference::variable('user'), 155 | Block::instantiate( 156 | "User" 157 | ) 158 | ), 159 | Block::invokeMethod( 160 | Reference::variable('user'), 161 | 'activate' 162 | ), 163 | ]; 164 | 165 | $block = Block::fromArray($expectedCode); 166 | 167 | 168 | $actualCode = $block->getCodes(); 169 | 170 | 171 | $this->assertEquals($expectedCode, $actualCode); 172 | } 173 | } -------------------------------------------------------------------------------- /src/ImperativeCode/Block.php: -------------------------------------------------------------------------------- 1 | code = $code; 27 | } 28 | 29 | public static function fromArray(array $code): Block 30 | { 31 | return new self($code); 32 | } 33 | 34 | public function addCode(ImperativeCode $code): self 35 | { 36 | $this->code[] = $code; 37 | 38 | return $this; 39 | } 40 | 41 | /** @param \Shomisha\Stubless\Abstractions\ImperativeCode[] $codes */ 42 | public function addCodes(array $codes): self 43 | { 44 | $this->code = array_merge($this->code, $codes); 45 | 46 | return $this; 47 | } 48 | 49 | /** @return \Shomisha\Stubless\Abstractions\ImperativeCode[] */ 50 | public function getCodes(): array 51 | { 52 | return $this->code; 53 | } 54 | 55 | public function getPrintableNodes(): array 56 | { 57 | $nodes = []; 58 | 59 | foreach ($this->code as $code) { 60 | $nodes = array_merge($nodes, $code->getPrintableNodes()); 61 | } 62 | 63 | return $nodes; 64 | } 65 | 66 | public function getImportSubDelegates(): array 67 | { 68 | return $this->code ?? []; 69 | } 70 | 71 | public static function assign($variable, $value): AssignBlock 72 | { 73 | if (!$variable instanceof AssignableContainer) { 74 | if (is_string($variable)) { 75 | $variable = Variable::name($variable); 76 | } else { 77 | throw new \InvalidArgumentException(sprintf( 78 | "Method %s::assign() expects string or instance of %s as first argument.", 79 | self::class, 80 | AssignableContainer::class, 81 | )); 82 | } 83 | } 84 | 85 | return new AssignBlock($variable, Value::normalize($value)); 86 | } 87 | 88 | public static function return($returnValue): ReturnBlock 89 | { 90 | return new ReturnBlock(Value::normalize($returnValue)); 91 | } 92 | 93 | /** @param \Shomisha\Stubless\Utilities\Importable|string $class */ 94 | public static function instantiate($class, array $arguments = []): InstantiateBlock 95 | { 96 | return new InstantiateBlock($class, $arguments); 97 | } 98 | 99 | public static function invokeMethod(ObjectContainer $variable, string $name, array $arguments = []): InvokeMethodBlock 100 | { 101 | return new InvokeMethodBlock($variable, $name, $arguments); 102 | } 103 | 104 | public static function invokeStaticMethod($class, string $name, array $arguments = []): InvokeStaticMethodBlock 105 | { 106 | return new InvokeStaticMethodBlock(ClassReference::normalize($class), $name, $arguments); 107 | } 108 | 109 | public static function invokeFunction(string $name, array $arguments = []): InvokeFunctionBlock 110 | { 111 | return new InvokeFunctionBlock($name, $arguments); 112 | } 113 | 114 | public static function throw(ObjectContainer $exception): ThrowBlock 115 | { 116 | return new ThrowBlock($exception); 117 | } 118 | 119 | public static function if($condition): IfBlock 120 | { 121 | return new IfBlock(AssignableValue::normalize($condition)); 122 | } 123 | 124 | public static function while($condition): WhileBlock 125 | { 126 | return new WhileBlock(AssignableValue::normalize($condition)); 127 | } 128 | 129 | public static function doWhile($condition): DoWhileBlock 130 | { 131 | return new DoWhileBlock(AssignableValue::normalize($condition)); 132 | } 133 | 134 | public static function foreach(Arrayable $array, $valueContainer): ForeachBlock 135 | { 136 | if (!$valueContainer instanceof Variable) { 137 | if (is_string($valueContainer)) { 138 | $valueContainer = Variable::name($valueContainer); 139 | } else { 140 | throw new \InvalidArgumentException(sprintf("Value cannot be safely normalized to a %s instance.", Value::class)); 141 | } 142 | } 143 | 144 | return new ForeachBlock($array, $valueContainer); 145 | } 146 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/IfBlockTest.php: -------------------------------------------------------------------------------- 1 | then(Block::fromArray([ 25 | Block::invokeFunction('doSomething'), 26 | ])); 27 | 28 | 29 | $printed = $ifBlock->print(); 30 | 31 | 32 | $this->assertStringContainsString("if ('test') {\n doSomething();\n}", $printed); 33 | } 34 | 35 | /** 36 | * @test 37 | * @dataProvider assignableValuesDataProvider 38 | */ 39 | public function user_can_create_if_block_using_any_assignable_value($value, string $printedValue) 40 | { 41 | $ifBlock = Block::if($value)->then(Block::fromArray([ 42 | Block::invokeFunction('doSomething') 43 | ])); 44 | 45 | 46 | $printed = $ifBlock->print(); 47 | 48 | 49 | $this->assertStringContainsString("if ({$printedValue}) {\n doSomething();\n}", $printed); 50 | } 51 | 52 | /** @test */ 53 | public function user_can_create_if_block_using_direct_constructor() 54 | { 55 | $ifBlock = new IfBlock( 56 | Block::invokeFunction('checkSomething'), 57 | Block::fromArray([Block::invokeFunction('doSomething')]) 58 | ); 59 | 60 | 61 | $printed = $ifBlock->print(); 62 | 63 | 64 | $this->assertStringContainsString("if (checkSomething()) {\n doSomething();\n}", $printed); 65 | } 66 | 67 | /**w 68 | * @test 69 | * @dataProvider imperativeCodeDataProvider 70 | */ 71 | public function user_can_use_any_imperative_code_as_if_body(ImperativeCode $code, string $printedCode) 72 | { 73 | $ifCode = Block::if(5); 74 | 75 | 76 | $ifCode->then($code); 77 | $printed = $ifCode->print(); 78 | 79 | 80 | $this->assertStringContainsString("if (5) {\n {$printedCode}\n}", $printed); 81 | } 82 | 83 | /** @test */ 84 | public function user_can_add_an_else_block_to_an_if_block() 85 | { 86 | $ifBlock = Block::if(Block::invokeFunction('checkSomething')); 87 | 88 | 89 | $ifBlock->else(Block::fromArray([ 90 | Block::invokeFunction('doSomething') 91 | ])); 92 | $printed = $ifBlock->print(); 93 | 94 | 95 | $this->assertStringContainsString("if (checkSomething()) {\n} else {\n doSomething();\n}", $printed); 96 | } 97 | 98 | /** @test */ 99 | public function user_can_add_elseif_blocks_to_if_block() 100 | { 101 | $ifBlock = Block::if(5)->then(Block::fromArray([ 102 | Block::invokeFunction('doSomething') 103 | ])); 104 | 105 | 106 | $ifBlock->elseif(3, Block::fromArray([ 107 | Block::invokeFunction('doSomethingElse') 108 | ])); 109 | $ifBlock->elseif(7, Block::fromArray([ 110 | Block::invokeFunction('doAnotherThing') 111 | ])); 112 | $printed = $ifBlock->print(); 113 | 114 | 115 | $this->assertStringContainsString("if (5) {\n doSomething();\n} elseif (3) {\n doSomethingElse();\n} elseif (7) {\n doAnotherThing();\n}", $printed); 116 | } 117 | 118 | /** @test */ 119 | public function if_block_will_delegate_imports_from_its_elements() 120 | { 121 | $ifBlock = Block::if(Reference::classReference(new Importable('App\Models\User')))->then( 122 | Block::invokeStaticMethod(new Importable('App\Models\Post'), 'publishAll') 123 | )->elseif( 124 | Block::invokeStaticMethod(new Importable('App\Models\Author'), 'exists'), 125 | Block::invokeStaticMethod(new Importable('App\Models\Book'), 'publishAll') 126 | ); 127 | 128 | 129 | $printed = $ifBlock->print(); 130 | 131 | 132 | $this->assertStringContainsString('use App\Models\User;', $printed); 133 | $this->assertStringContainsString('if (User::class)', $printed); 134 | $this->assertStringContainsString('use App\Models\Post;', $printed); 135 | $this->assertStringContainsString('Post::publishAll();', $printed); 136 | $this->assertStringContainsString('use App\Models\Author', $printed); 137 | $this->assertStringContainsString('Author::exists()', $printed); 138 | $this->assertStringContainsString('use App\Models\Book', $printed); 139 | $this->assertStringContainsString('Book::publishAll()', $printed); 140 | } 141 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/WhileBlockTest.php: -------------------------------------------------------------------------------- 1 | do(Block::fromArray([ 24 | Block::invokeFunction('doSomething'), 25 | ])); 26 | 27 | 28 | $printed = $whileBlock->print(); 29 | 30 | 31 | $this->assertStringContainsString("while ('test') {\n doSomething();\n}", $printed); 32 | } 33 | 34 | /** 35 | * @test 36 | * @dataProvider assignableValuesDataProvider 37 | */ 38 | public function user_can_create_while_block_using_any_assignable_values($value, string $printedValue) 39 | { 40 | $whileBlock = Block::while($value)->do(Block::fromArray([ 41 | Block::invokeFunction('doSomething') 42 | ])); 43 | 44 | 45 | $printed = $whileBlock->print(); 46 | 47 | 48 | $this->assertStringContainsString("while ({$printedValue}) {\n doSomething();\n}", $printed); 49 | } 50 | 51 | /** @test */ 52 | public function user_can_create_while_block_using_direct_constructor() 53 | { 54 | $whileBlock = new WhileBlock( 55 | Block::invokeFunction('checkSomething'), 56 | Block::fromArray([Block::invokeFunction('doSomething')]) 57 | ); 58 | 59 | 60 | $printed = $whileBlock->print(); 61 | 62 | 63 | $this->assertStringContainsString("while (checkSomething()) {\n doSomething();\n}", $printed); 64 | } 65 | 66 | /** 67 | * @test 68 | * @dataProvider imperativeCodeDataProvider 69 | */ 70 | public function user_can_use_any_imperative_code_as_while_body(ImperativeCode $code, string $printedCode) 71 | { 72 | $whileBlock = Block::while(5); 73 | 74 | 75 | $whileBlock->do($code); 76 | $printed = $whileBlock->print(); 77 | 78 | 79 | $this->assertStringContainsString("while (5) {\n {$printedCode}\n}", $printed); 80 | } 81 | 82 | /** @test */ 83 | public function while_block_will_delegate_imports_from_its_elements() 84 | { 85 | $ifBlock = Block::while(Reference::classReference(new Importable('App\Models\User')))->do(Block::fromArray([ 86 | Block::invokeStaticMethod(new Importable('App\Models\Author'), 'exists'), 87 | Block::invokeStaticMethod(new Importable('App\Models\Book'), 'publishAll') 88 | ])); 89 | 90 | 91 | $printed = $ifBlock->print(); 92 | 93 | 94 | $this->assertStringContainsString('use App\Models\User;', $printed); 95 | $this->assertStringContainsString('while (User::class)', $printed); 96 | $this->assertStringContainsString('use App\Models\Author', $printed); 97 | $this->assertStringContainsString('Author::exists()', $printed); 98 | $this->assertStringContainsString('use App\Models\Book', $printed); 99 | $this->assertStringContainsString('Book::publishAll()', $printed); 100 | } 101 | 102 | /** @test */ 103 | public function do_while_block_will_delegate_imports_from_its_elements() 104 | { 105 | $ifBlock = Block::doWhile(Reference::classReference(new Importable('App\Models\User')))->do(Block::fromArray([ 106 | Block::invokeStaticMethod(new Importable('App\Models\Author'), 'exists'), 107 | Block::invokeStaticMethod(new Importable('App\Models\Book'), 'publishAll') 108 | ])); 109 | 110 | 111 | $printed = $ifBlock->print(); 112 | 113 | 114 | $this->assertStringContainsString('use App\Models\User;', $printed); 115 | $this->assertStringContainsString('while (User::class)', $printed); 116 | $this->assertStringContainsString('use App\Models\Author', $printed); 117 | $this->assertStringContainsString('Author::exists()', $printed); 118 | $this->assertStringContainsString('use App\Models\Book', $printed); 119 | $this->assertStringContainsString('Book::publishAll()', $printed); 120 | } 121 | 122 | /** @test */ 123 | public function user_can_create_do_while_block_using_factory_method() 124 | { 125 | $doWhileBlock = Block::doWhile('test')->do(Block::fromArray([ 126 | Block::invokeFunction('doSomething'), 127 | ])); 128 | 129 | 130 | $printed = $doWhileBlock->print(); 131 | 132 | 133 | $this->assertStringContainsString("do {\n doSomething();\n} while ('test');", $printed); 134 | } 135 | 136 | /** 137 | * @test 138 | * @dataProvider assignableValuesDataProvider 139 | */ 140 | public function user_can_create_do_while_block_using_any_assignable_value($value, string $printedValue) 141 | { 142 | $doWhileBlock = Block::doWhile($value)->do(Block::fromArray([ 143 | Block::invokeFunction('doSomething') 144 | ])); 145 | 146 | 147 | $printed = $doWhileBlock->print(); 148 | 149 | 150 | $this->assertStringContainsString("do {\n doSomething();\n} while ({$printedValue});", $printed); 151 | } 152 | 153 | /** @test */ 154 | public function user_can_create_do_while_block_using_direct_constructor() 155 | { 156 | $doWhileBlock = new DoWhileBlock( 157 | Block::invokeFunction('checkSomething'), 158 | Block::fromArray([Block::invokeFunction('doSomething')]) 159 | ); 160 | 161 | 162 | $printed = $doWhileBlock->print(); 163 | 164 | 165 | $this->assertStringContainsString("do {\n doSomething();\n} while (checkSomething());", $printed); 166 | } 167 | 168 | /** 169 | * @test 170 | * @dataProvider imperativeCodeDataProvider 171 | */ 172 | public function user_can_use_any_imperative_code_as_do_while_body(ImperativeCode $code, string $printedCode) 173 | { 174 | $doWhileBlock = Block::doWhile(5); 175 | 176 | 177 | $doWhileBlock->do($code); 178 | $printed = $doWhileBlock->print(); 179 | 180 | 181 | $this->assertStringContainsString("do {\n {$printedCode}\n} while (5);", $printed); 182 | } 183 | } -------------------------------------------------------------------------------- /tests/Concerns/ImperativeCodeDataProviders.php: -------------------------------------------------------------------------------- 1 | [Block::invokeFunction('doSomething'), 'doSomething();'], 16 | 'Invoke static method' => [Block::invokeStaticMethod(Reference::staticReference(), 'doSomething'), 'static::doSomething();'], 17 | 'Invoke method' => [Block::invokeMethod(Reference::this(), 'doSomething'), '$this->doSomething();'], 18 | 'Return value' => [Block::return(15), 'return 15;'], 19 | 'Assign value' => [Block::assign(Reference::objectProperty(Reference::this(), 'someProperty'), 'someValue'), "\$this->someProperty = 'someValue';"], 20 | 'Standalone reference' => [Reference::variable('test'), '$test;'], 21 | 'Standalone value' => [Value::string('I am alone.'), "'I am alone.';"], 22 | 'Block of code' => [ 23 | Block::fromArray([ 24 | Block::assign(Reference::variable('user'), Block::invokeStaticMethod('User', 'find', [22])), 25 | Block::invokeMethod(Reference::variable('user'), 'deactivate'), 26 | Block::return(Reference::variable('user')), 27 | ]), 28 | "\$user = User::find(22);\n \$user->deactivate();\n\n return \$user;" 29 | ] 30 | ]; 31 | } 32 | 33 | public function referencesDataProvider() 34 | { 35 | return [ 36 | "Variable" => [Reference::variable('testVar'), '$testVar'], 37 | "This" => [Reference::this(), '$this'], 38 | "Array Key Reference" => [Reference::arrayFetch(Reference::variable('testArray'), 'testKey'), "\$testArray['testKey']"], 39 | "Object Property" => [Reference::objectProperty(Reference::variable('testVar'), 'testProperty'), '$testVar->testProperty'], 40 | "Class Reference" => [Reference::classReference('User'), 'User::class'], 41 | "Static Reference" => [Reference::staticReference(), 'static::class'], 42 | "Self Reference" => [Reference::selfReference(), 'self::class'], 43 | "Static Property" => [Reference::staticProperty('User', 'totalCount'), 'User::$totalCount'], 44 | ]; 45 | } 46 | 47 | public function invocationsDataProvider() 48 | { 49 | return [ 50 | "Function" => [Block::invokeFunction('doSomething', [5]), 'doSomething(5)'], 51 | "Method" => [Block::invokeMethod(Reference::this(), 'doSomethingElse', ['test']), "\$this->doSomethingElse('test')"], 52 | "Static method" => [Block::invokeStaticMethod('User', 'setAvailable', [true]), 'User::setAvailable(true)'], 53 | "Instantiation" => [Block::instantiate('User'), 'new User()'], 54 | ]; 55 | } 56 | 57 | public function comparisonsDataProvider() 58 | { 59 | return [ 60 | 'Equals' => [Comparison::equals(1, 5), '1 == 5'], 61 | 'Equals strict' => [Comparison::equalsStrict(1, '1'), "1 === '1'"], 62 | 'Not equals' => [Comparison::notEquals(2, 2), '2 != 2'], 63 | 'Not equals strict' => [Comparison::notEqualsStrict(4, '4'), "4 !== '4'"], 64 | 'Greater than' => [Comparison::greaterThan('test', 3), "'test' > 3"], 65 | 'Greater than equals' => [Comparison::greaterThanEquals(4, 4), '4 >= 4'], 66 | 'Lesser than' => [Comparison::lesserThan(4, 2), '4 < 2'], 67 | 'Lesser than equals' => [Comparison::lesserThanEquals('asd', 'dsa'), "'asd' <= 'dsa'"], 68 | ]; 69 | } 70 | 71 | public function primeValuesDataProvider() 72 | { 73 | return [ 74 | "String" => ["test string", "'test string'"], 75 | "Wrapped String" => [Value::string("another test string"), "'another test string'"], 76 | "Integer" => [1, '1'], 77 | "Wrapped Integer" => [Value::integer(1), '1'], 78 | "Float" => [3.14, "3.14"], 79 | "Wrapped Float" => [Value::float(24.42), "24.42"], 80 | "Array" => [[1, 2, 3], "[1, 2, 3]"], 81 | "Wrapped Array" => [Value::array([1, 'string', false]), "[1, 'string', false]"], 82 | "Boolean" => [true, "true"], 83 | "Wrapped Boolean" => [Value::boolean(false), "false"], 84 | ]; 85 | } 86 | 87 | public function arrayablesDataProvider() 88 | { 89 | return [ 90 | 'Invoke function' => [Block::invokeFunction('doSomething'), 'doSomething()'], 91 | 'Invoke static method' => [Block::invokeStaticMethod(Reference::staticReference(), 'doSomething'), 'static::doSomething()'], 92 | 'Invoke method' => [Block::invokeMethod(Reference::this(), 'doSomething'), '$this->doSomething()'], 93 | "Variable" => [Reference::variable('testVar'), '$testVar'], 94 | "Object Property" => [Reference::objectProperty(Reference::variable('testVar'), 'testProperty'), '$testVar->testProperty'], 95 | "Static Property" => [Reference::staticProperty('User', 'totalCount'), 'User::$totalCount'], 96 | ]; 97 | } 98 | 99 | public function assignableValuesDataProvider() 100 | { 101 | return array_merge( 102 | $this->referencesDataProvider(), 103 | $this->invocationsDataProvider(), 104 | $this->comparisonsDataProvider(), 105 | $this->primeValuesDataProvider(), 106 | ); 107 | } 108 | 109 | public function assignableContainersDataProvider() 110 | { 111 | return [ 112 | 'Variable' => [Reference::variable('test'), "\$test"], 113 | 'Array key' => [Reference::arrayFetch(Reference::variable('testArray'), 'testKey'), "\$testArray['testKey']"], 114 | 'Object property' => [Reference::objectProperty(Reference::variable('testObject'), 'testProperty'), "\$testObject->testProperty"], 115 | 'Static property' => [Reference::staticProperty('TestClass', 'testProperty'), "TestClass::\$testProperty"], 116 | ]; 117 | } 118 | 119 | public function objectContainersDataProvider() 120 | { 121 | return [ 122 | 'Variable' => [Reference::variable('test'), "\$test"], 123 | 'Array key' => [Reference::arrayFetch(Reference::variable('testArray'), 'testKey'), "\$testArray['testKey']"], 124 | 'Object property' => [Reference::objectProperty(Reference::variable('testObject'), 'testProperty'), "\$testObject->testProperty"], 125 | 'Static property' => [Reference::staticProperty('TestClass', 'testProperty'), "TestClass::\$testProperty"], 126 | 'Function invocation' => [Block::invokeFunction('getFactory'), 'getFactory()'], 127 | 'Method invocation' => [Block::invokeMethod(Reference::this(), 'getFactory'), '$this->getFactory()'], 128 | 'Static method invocation' => [Block::invokeStaticMethod(Reference::classReference('SomeClass'), 'getFactory'), 'SomeClass::getFactory()'], 129 | 'Instantiations' => [Block::instantiate('SomeClass'), '(new SomeClass())'], 130 | ]; 131 | } 132 | } -------------------------------------------------------------------------------- /src/DeclarativeCode/ClassTemplate.php: -------------------------------------------------------------------------------- 1 | name = $name; 38 | $this->extends = $extends; 39 | } 40 | 41 | public function extends($extends = null) 42 | { 43 | if ($extends === null) { 44 | return $this->getExtends(); 45 | } 46 | 47 | return $this->setExtends($extends); 48 | } 49 | 50 | public function getExtends(): ?string 51 | { 52 | return $this->extends; 53 | } 54 | 55 | public function setExtends($extends): self 56 | { 57 | $this->extends = (string) $extends; 58 | 59 | if ($this->isImportable($extends)) { 60 | $this->addImportable($extends); 61 | } 62 | 63 | return $this; 64 | } 65 | 66 | public function implements(array $interfaces = null) 67 | { 68 | if ($interfaces !== null) { 69 | return $this->withInterfaces($interfaces); 70 | } 71 | 72 | return $this->getInterfaces(); 73 | } 74 | 75 | /** @param string|\Shomisha\Stubless\Utilities\Importable $interface */ 76 | public function addInterface($interface): self 77 | { 78 | $actualInterface = (string) $interface; 79 | 80 | $this->interfaces[$actualInterface] = $actualInterface; 81 | 82 | if ($this->isImportable($interface)) { 83 | $this->addImportable($interface); 84 | } 85 | 86 | return $this; 87 | } 88 | 89 | public function removeInterface(string $interfaceName): self 90 | { 91 | unset($this->interfaces[$interfaceName]); 92 | 93 | return $this; 94 | } 95 | 96 | public function withInterfaces(array $interfaces): self 97 | { 98 | $this->interfaces = []; 99 | 100 | foreach ($interfaces as $interface) { 101 | $this->addInterface($interface); 102 | } 103 | 104 | return $this; 105 | } 106 | 107 | /** @return string[] */ 108 | public function getInterfaces(): array 109 | { 110 | return $this->interfaces; 111 | } 112 | 113 | public function uses(array $traits = null) 114 | { 115 | if ($traits === null) { 116 | return $this->getTraits(); 117 | } 118 | 119 | return $this->withTraits($traits); 120 | } 121 | 122 | /** @param string|\Shomisha\Stubless\Utilities\Importable $trait */ 123 | public function addTrait($trait): self 124 | { 125 | $actualTrait = (string) $trait; 126 | 127 | $this->traits[$actualTrait] = $actualTrait; 128 | 129 | if ($this->isImportable($trait)) { 130 | $this->addImportable($trait); 131 | } 132 | 133 | return $this; 134 | } 135 | 136 | public function removeTrait(string $trait): self 137 | { 138 | unset($this->traits[$trait]); 139 | 140 | return $this; 141 | } 142 | 143 | public function withTraits(array $traits): self 144 | { 145 | $this->traits = []; 146 | 147 | foreach ($traits as $trait) { 148 | $this->addTrait($trait); 149 | } 150 | 151 | return $this; 152 | } 153 | 154 | public function getTraits(): array 155 | { 156 | return $this->traits; 157 | } 158 | 159 | public function constants(array $constants = null) 160 | { 161 | if ($constants !== null) { 162 | return $this->withConstants($constants); 163 | } 164 | 165 | return $this->getConstants(); 166 | } 167 | 168 | public function addConstant(ClassConstant $constant): self 169 | { 170 | $this->constants[$constant->getName()] = $constant; 171 | 172 | return $this; 173 | } 174 | 175 | public function removeConstant(string $constantName): self 176 | { 177 | unset($this->constants[$constantName]); 178 | 179 | return $this; 180 | } 181 | 182 | /** @param \Shomisha\Stubless\DeclarativeCode\ClassConstant[] $constants */ 183 | public function withConstants(array $constants): self 184 | { 185 | $this->validateArrayElements($constants, ClassConstant::class); 186 | 187 | $this->constants = []; 188 | 189 | foreach ($constants as $constant) { 190 | $this->addConstant($constant); 191 | } 192 | 193 | return $this; 194 | } 195 | 196 | /** @return \Shomisha\Stubless\DeclarativeCode\ClassConstant[] */ 197 | public function getConstants(): array 198 | { 199 | return $this->constants; 200 | } 201 | 202 | public function withDefaultDocBlock(): self 203 | { 204 | return $this->withDocBlock("Class {$this->name}"); 205 | } 206 | 207 | public function getPrintableNodes(): array 208 | { 209 | return array_values( 210 | array_filter([ 211 | $this->constructNamespaceNode(), 212 | ...$this->constructImportNodes(), 213 | $this->constructClassNode(), 214 | ]) 215 | ); 216 | } 217 | 218 | protected function constructClassNode(): Node 219 | { 220 | $class = $this->getFactory()->class($this->name); 221 | 222 | if (!empty($this->interfaces)) { 223 | $class->implement(...array_values($this->interfaces)); 224 | } 225 | 226 | $this->makeBuilderFinal($class); 227 | $this->makeBuilderAbstract($class); 228 | 229 | if ($this->extends !== null) { 230 | $class->extend($this->extends); 231 | } 232 | 233 | if (!empty($this->traits)) { 234 | $class->addStmt($this->getFactory()->useTrait(...array_values($this->traits))); 235 | } 236 | 237 | foreach ($this->constants as $constant) { 238 | $class->addStmt($constant->getPrintableNodes()[0]); 239 | } 240 | 241 | $this->addPropertiesToDeclaration($class); 242 | $this->addMethodsToDeclaration($class); 243 | 244 | $this->setDocBlockCommentOnBuilder($class); 245 | 246 | return $this->convertBuilderToNode($class); 247 | } 248 | 249 | protected function constructNamespaceNode(): ?Node 250 | { 251 | if ($this->hasNamespace()) { 252 | return $this->convertBuilderToNode( 253 | $this->getFactory()->namespace($this->namespace) 254 | ); 255 | } 256 | 257 | return null; 258 | } 259 | 260 | /** @return \PhpParser\Node\Stmt\Use_[] */ 261 | protected function constructImportNodes(): array 262 | { 263 | $imports = []; 264 | 265 | foreach ($this->gatherAllImports() as $import) { 266 | $imports[] = $import->getPrintableNodes()[0]; 267 | } 268 | 269 | return $imports; 270 | } 271 | 272 | /** @return \Shomisha\Stubless\ImperativeCode\UseStatement[] */ 273 | protected function gatherAllImports(): array 274 | { 275 | return array_merge( 276 | $this->imports, 277 | $this->gatherImportsFromDelegates($this->properties), 278 | $this->gatherImportsFromDelegates($this->methods), 279 | ); 280 | } 281 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/ComparisonTest.php: -------------------------------------------------------------------------------- 1 | print(); 33 | 34 | 35 | $this->assertStringContainsString("'test' == 'anothertest';", $printed); 36 | } 37 | 38 | /** @test */ 39 | public function user_can_perform_equal_comparison_using_direct_constructor() 40 | { 41 | $comparison = new Equals(AssignableValue::normalize('test'), AssignableValue::normalize('anothertest')); 42 | 43 | 44 | $printed = $comparison->print(); 45 | 46 | 47 | $this->assertStringContainsString("'test' == 'anothertest';", $printed); 48 | } 49 | 50 | /** @test */ 51 | public function user_can_perform_strict_equal_comparisons() 52 | { 53 | $comparison = Comparison::equalsStrict(5, 7); 54 | 55 | 56 | $printed = $comparison->print(); 57 | 58 | 59 | $this->assertStringContainsString('5 === 7;', $printed); 60 | } 61 | 62 | /** @test */ 63 | public function user_can_perform_strict_equal_comparisons_using_direct_constructor() 64 | { 65 | $comparison = new EqualsStrict(AssignableValue::normalize(5), AssignableValue::normalize(7)); 66 | 67 | 68 | $printed = $comparison->print(); 69 | 70 | 71 | $this->assertStringContainsString('5 === 7;', $printed); 72 | } 73 | 74 | /** @test */ 75 | public function user_can_perform_not_equal_comparison() 76 | { 77 | $comparison = Comparison::notEquals('asd', 'dsa'); 78 | 79 | 80 | $printed = $comparison->print(); 81 | 82 | 83 | $this->assertStringContainsString("'asd' != 'dsa';", $printed); 84 | } 85 | 86 | /** @test */ 87 | public function user_can_perform_not_equal_comparison_using_direct_constructor() 88 | { 89 | $comparison = new NotEquals(AssignableValue::normalize('asd'), AssignableValue::normalize('dsa')); 90 | 91 | 92 | $printed = $comparison->print(); 93 | 94 | 95 | $this->assertStringContainsString("'asd' != 'dsa';", $printed); 96 | } 97 | 98 | /** @test */ 99 | public function user_can_perform_not_equal_strict_comparisons() 100 | { 101 | $comparison = Comparison::notEqualsStrict(5, '5'); 102 | 103 | 104 | $printed = $comparison->print(); 105 | 106 | 107 | $this->assertStringContainsString("5 !== '5';", $printed); 108 | } 109 | 110 | /** @test */ 111 | public function user_can_perform_not_equal_strict_comparisons_using_direct_constructor() 112 | { 113 | $comparison = new NotEqualsStrict(AssignableValue::normalize(5), AssignableValue::normalize('5')); 114 | 115 | 116 | $printed = $comparison->print(); 117 | 118 | 119 | $this->assertStringContainsString("5 !== '5';", $printed); 120 | } 121 | 122 | /** @test */ 123 | public function user_can_perform_greater_than_comparisons() 124 | { 125 | $comparison = Comparison::greaterThan(22, 21); 126 | 127 | 128 | $printed = $comparison->print(); 129 | 130 | 131 | $this->assertStringContainsString('22 > 21;', $printed); 132 | } 133 | 134 | /** @test */ 135 | public function user_can_perform_greater_than_comparisons_using_direct_constructor() 136 | { 137 | $comparison = new GreaterThan(AssignableValue::normalize(22), AssignableValue::normalize(21)); 138 | 139 | 140 | $printed = $comparison->print(); 141 | 142 | 143 | $this->assertStringContainsString('22 > 21;', $printed); 144 | } 145 | 146 | /** @test */ 147 | public function user_can_perform_greater_than_equal_comparisons() 148 | { 149 | $comparison = Comparison::greaterThanEquals('someString', 'anotherString'); 150 | 151 | 152 | $printed = $comparison->print(); 153 | 154 | 155 | $this->assertStringContainsString("'someString' >= 'anotherString';", $printed); 156 | } 157 | 158 | /** @test */ 159 | public function user_can_perform_greater_than_equal_comparisons_using_direct_constructor() 160 | { 161 | $comparison = new GreaterThanEquals(AssignableValue::normalize('someString'), AssignableValue::normalize('anotherString')); 162 | 163 | 164 | $printed = $comparison->print(); 165 | 166 | 167 | $this->assertStringContainsString("'someString' >= 'anotherString';", $printed); 168 | } 169 | 170 | /** @test */ 171 | public function user_can_perform_lesser_than_comparisons() 172 | { 173 | $comparison = Comparison::lesserThan(22, 21); 174 | 175 | 176 | $printed = $comparison->print(); 177 | 178 | 179 | $this->assertStringContainsString('22 < 21;', $printed); 180 | } 181 | 182 | /** @test */ 183 | public function user_can_perform_lesser_than_comparisons_using_direct_constructor() 184 | { 185 | $comparison = new LesserThan(AssignableValue::normalize(22), AssignableValue::normalize(21)); 186 | 187 | 188 | $printed = $comparison->print(); 189 | 190 | 191 | $this->assertStringContainsString('22 < 21;', $printed); 192 | } 193 | 194 | /** @test */ 195 | public function user_can_perform_lesser_than_equal_comparisons() 196 | { 197 | $comparison = Comparison::lesserThanEquals(1, 'less than one'); 198 | 199 | 200 | $printed = $comparison->print(); 201 | 202 | 203 | $this->assertStringContainsString("1 <= 'less than one'", $printed); 204 | } 205 | 206 | /** @test */ 207 | public function user_can_perform_lesser_than_equal_comparisons_using_direct_constructor() 208 | { 209 | $comparison = new LesserThanEquals(AssignableValue::normalize(1), AssignableValue::normalize('less than one')); 210 | 211 | 212 | $printed = $comparison->print(); 213 | 214 | 215 | $this->assertStringContainsString("1 <= 'less than one'", $printed); 216 | } 217 | 218 | /** 219 | * @test 220 | * @dataProvider referencesDataProvider 221 | */ 222 | public function user_can_perform_comparisons_with_references(Reference $reference, string $printedReference) 223 | { 224 | $comparison = Comparison::notEqualsStrict($reference, 'testValue'); 225 | 226 | 227 | $printed = $comparison->print(); 228 | 229 | 230 | $this->assertStringContainsString("{$printedReference} !== 'testValue'", $printed); 231 | } 232 | 233 | /** 234 | * @test 235 | * @dataProvider invocationsDataProvider 236 | */ 237 | public function user_can_perform_comparisons_with_invocations(InvokeBlock $invocation, string $printedInvocation) 238 | { 239 | $comparison = Comparison::greaterThanEquals('Am I Your Equal?', $invocation); 240 | 241 | 242 | $printed = $comparison->print(); 243 | 244 | 245 | $this->assertStringContainsString("'Am I Your Equal?' >= {$printedInvocation};", $printed); 246 | } 247 | 248 | /** 249 | * @test 250 | * @dataProvider primeValuesDataProvider 251 | */ 252 | public function user_can_perform_comparisons_with_prime_values($value, string $printedValue) 253 | { 254 | $comparison = Comparison::lesserThan($value, 24); 255 | 256 | 257 | $printed = $comparison->print(); 258 | 259 | 260 | $this->assertStringContainsString("{$printedValue} < 24;", $printed); 261 | } 262 | 263 | /** 264 | * @test 265 | * @dataProvider comparisonsDataProvider 266 | */ 267 | public function user_can_perform_comparisons_with_other_comparisons(Comparison $otherComparison, string $printedOtherComparison) 268 | { 269 | $mainComparison = Comparison::greaterThan(22, $otherComparison); 270 | 271 | 272 | $printed = $mainComparison->print(); 273 | 274 | 275 | $this->assertStringContainsString("22 > ({$printedOtherComparison});", $printed); 276 | } 277 | 278 | /** @test */ 279 | public function user_can_create_not_comparison_using_direct_constructor() 280 | { 281 | $notComparison = new Not(Block::invokeStaticMethod( 282 | 'User', 283 | 'exists', 284 | [5] 285 | )); 286 | 287 | 288 | $printed = $notComparison->print(); 289 | 290 | 291 | $this->assertStringContainsString("!User::exists(5);", $printed); 292 | } 293 | 294 | /** @test */ 295 | public function user_can_create_not_comparison_using_factory_method() 296 | { 297 | $notComparison = Comparison::not( 298 | Block::invokeFunction('array_key_exists', ['some_property', Reference::variable('override')]) 299 | ); 300 | 301 | 302 | $printed = $notComparison->print(); 303 | 304 | 305 | $this->assertStringContainsString("!array_key_exists('some_property', \$override);", $printed); 306 | } 307 | 308 | /** 309 | * @test 310 | * @dataProvider assignableValuesDataProvider 311 | */ 312 | public function user_can_create_not_comparison_using_any_assignable_value($assignableValue, string $printedAssignableValue) 313 | { 314 | $notComparison = Comparison::not($assignableValue); 315 | 316 | 317 | $printed = $notComparison->print(); 318 | 319 | 320 | if ($assignableValue instanceof Comparison) { 321 | $printedAssignableValue = "({$printedAssignableValue})"; 322 | } 323 | 324 | $this->assertStringContainsString("!{$printedAssignableValue};", $printed); 325 | } 326 | 327 | /** @test */ 328 | public function not_comparison_can_be_doubly_negated() 329 | { 330 | $notComparison = Comparison::not('test'); 331 | 332 | 333 | $notComparison->negate(); 334 | $printed = $notComparison->print(); 335 | 336 | 337 | $this->assertStringContainsString("!!'test';", $printed); 338 | } 339 | } -------------------------------------------------------------------------------- /tests/Unit/Declarative/ClassPropertyTest.php: -------------------------------------------------------------------------------- 1 | makePrivate(); 20 | 21 | $property->type('string'); 22 | 23 | $property->value('testValue'); 24 | 25 | 26 | $printed = $property->print(); 27 | 28 | 29 | $this->assertStringContainsString('private string $testProperty = \'testValue\';', $printed); 30 | } 31 | 32 | /** @test */ 33 | public function user_can_create_public_class_property() 34 | { 35 | $property = ClassProperty::name('publicProperty')->makePublic(); 36 | 37 | 38 | $printed = $property->print(); 39 | 40 | 41 | $this->assertStringContainsString('public $publicProperty;', $printed); 42 | } 43 | 44 | /** @test */ 45 | public function user_can_check_if_class_property_is_public() 46 | { 47 | $property = ClassProperty::name('test')->makeProtected(); 48 | 49 | 50 | $isPublic = $property->isPublic(); 51 | 52 | 53 | $this->assertFalse($isPublic); 54 | } 55 | 56 | /** @test */ 57 | public function user_can_create_protected_class_property() 58 | { 59 | $property = ClassProperty::name('protectedProperty')->makeProtected(); 60 | 61 | 62 | $printed = $property->print(); 63 | 64 | 65 | $this->assertStringContainsString('protected $protectedProperty;', $printed); 66 | } 67 | 68 | /** @test */ 69 | public function user_can_check_if_class_property_is_protected() 70 | { 71 | $property = ClassProperty::name('test')->makeProtected(); 72 | 73 | 74 | $isProtected = $property->isProtected(); 75 | 76 | 77 | $this->assertTrue($isProtected); 78 | } 79 | 80 | /** @test */ 81 | public function user_can_create_private_class_property() 82 | { 83 | $property = ClassProperty::name('privateProperty')->makePrivate(); 84 | 85 | 86 | $printed = $property->print(); 87 | 88 | 89 | $this->assertStringContainsString('private $privateProperty;', $printed); 90 | } 91 | 92 | /** @test */ 93 | public function user_can_check_if_class_property_is_private() 94 | { 95 | $property = ClassProperty::name('test')->makePublic(); 96 | 97 | 98 | $isPrivate = $property->isPrivate(); 99 | 100 | 101 | $this->assertFalse($isPrivate); 102 | } 103 | 104 | /** @test */ 105 | public function user_can_set_property_access_modifier_explicitly() 106 | { 107 | $property = ClassProperty::name('test'); 108 | 109 | 110 | $property->setAccess(ClassAccess::PROTECTED()); 111 | 112 | 113 | $this->assertTrue($property->isProtected()); 114 | } 115 | 116 | /** @test */ 117 | public function user_can_get_property_access_modifier() 118 | { 119 | $property = ClassProperty::name('test')->makeProtected(); 120 | 121 | 122 | $access = $property->getAccess(); 123 | 124 | 125 | $this->assertInstanceOf(ClassAccess::class, $access); 126 | $this->assertEquals('protected', $access->value()); 127 | } 128 | 129 | /** @test */ 130 | public function user_can_create_class_property_with_importable_type() 131 | { 132 | $property = ClassProperty::name('someProperty')->type( 133 | new Importable(\App\Models\Vehicle::class) 134 | ); 135 | 136 | $class = ClassTemplate::name('TestClass')->setNamespace('Test'); 137 | 138 | $class->addProperty($property); 139 | 140 | 141 | $printed = $class->print(); 142 | 143 | 144 | $this->assertStringContainsString('use App\Models\Vehicle;', $printed); 145 | $this->assertStringContainsString('public Vehicle $someProperty;', $printed); 146 | } 147 | 148 | /** @test */ 149 | public function user_can_create_class_property_without_type() 150 | { 151 | $property = ClassProperty::name('test'); 152 | 153 | 154 | $printed = $property->print(); 155 | 156 | 157 | $this->assertStringContainsString('public $test;', $printed); 158 | } 159 | 160 | /** @test */ 161 | public function user_can_get_the_class_property_type() 162 | { 163 | $property = ClassProperty::name('test'); 164 | 165 | $property->setType('string'); 166 | 167 | 168 | $type = $property->getType(); 169 | 170 | 171 | $this->assertEquals('string', $type); 172 | } 173 | 174 | /** @test */ 175 | public function user_can_get_the_class_property_type_using_fluent_setter() 176 | { 177 | $property = ClassProperty::name('test'); 178 | 179 | $property->setType('array'); 180 | 181 | 182 | $type = $property->type(); 183 | 184 | 185 | $this->assertEquals('array', $type); 186 | } 187 | 188 | /** @test */ 189 | public function user_can_create_class_property_without_value() 190 | { 191 | $property = ClassProperty::name('test'); 192 | 193 | 194 | $printed = $property->print(); 195 | 196 | 197 | $this->assertStringContainsString('public $test;', $printed); 198 | } 199 | 200 | /** @test */ 201 | public function user_cannot_set_objects_as_value_to_class_property() 202 | { 203 | $property = ClassProperty::name('test'); 204 | 205 | 206 | $this->expectException(\InvalidArgumentException::class); 207 | 208 | 209 | $property->value(new \stdClass()); 210 | } 211 | 212 | /** @test */ 213 | public function user_can_user_importables_as_class_property_values() 214 | { 215 | $classTemplate = ClassTemplate::name('SomeClass')->withProperties([ 216 | ClassProperty::name('anotherClassName')->value(new Importable('App\Services\AnotherClass')) 217 | ]); 218 | 219 | $printed = $classTemplate->print(); 220 | 221 | 222 | $this->assertStringContainsString('use App\Services\AnotherClass;', $printed); 223 | $this->assertStringContainsString("public \$anotherClassName = AnotherClass::class", $printed); 224 | } 225 | 226 | /** @test */ 227 | public function user_can_use_class_references_as_class_property_values() 228 | { 229 | $classTemplate = ClassTemplate::name('SomeCLass')->withProperties([ 230 | ClassProperty::name('anotherClassName')->value(Reference::classReference('AnotherClass')) 231 | ]); 232 | 233 | 234 | $printed = $classTemplate->print(); 235 | 236 | 237 | $this->assertStringContainsString('public $anotherClassName = AnotherClass::class', $printed); 238 | } 239 | 240 | /** @test */ 241 | public function user_can_get_the_class_property_value() 242 | { 243 | $property = ClassProperty::name('test')->setValue(15); 244 | 245 | 246 | $value = $property->getValue(); 247 | 248 | 249 | $this->assertEquals(15, $value); 250 | } 251 | 252 | /** @test */ 253 | public function user_can_get_the_class_property_value_using_fluent_setter() 254 | { 255 | $property = ClassProperty::name('test')->setValue(false); 256 | 257 | 258 | $value = $property->value(); 259 | 260 | 261 | $this->assertEquals(false, $value); 262 | } 263 | 264 | /** @test */ 265 | public function user_can_make_properties_static() 266 | { 267 | $property = ClassProperty::name('staticProperty'); 268 | 269 | 270 | $property->makeStatic(); 271 | $printed = $property->print(); 272 | 273 | 274 | $this->assertStringContainsString('public static $staticProperty;', $printed); 275 | } 276 | 277 | /** @test */ 278 | public function user_can_make_properties_static_using_the_fluent_alias() 279 | { 280 | $property = ClassProperty::name('staticProperty'); 281 | 282 | 283 | $property->static(true); 284 | $printed = $property->print(); 285 | 286 | 287 | $this->assertStringContainsString('public static $staticProperty;', $printed); 288 | } 289 | 290 | /** 291 | * @test 292 | * @testWith [true] 293 | * [false] 294 | */ 295 | public function user_can_check_if_properties_are_static($shouldBeStatic) 296 | { 297 | $property = ClassProperty::name('staticProperty')->makeStatic($shouldBeStatic); 298 | 299 | 300 | $isStatic = $property->isStatic(); 301 | 302 | 303 | $this->assertEquals($shouldBeStatic, $isStatic); 304 | } 305 | 306 | /** 307 | * @test 308 | * @testWith [true] 309 | * [false] 310 | */ 311 | public function user_can_check_if_properties_are_static_using_the_fluent_alias($shouldBeStatic) 312 | { 313 | $property = ClassProperty::name('staticProperty')->makeStatic($shouldBeStatic); 314 | 315 | 316 | $isStatic = $property->static(); 317 | 318 | 319 | $this->assertEquals($shouldBeStatic, $isStatic); 320 | } 321 | 322 | /** @test */ 323 | public function class_property_can_have_doc_block() 324 | { 325 | $classProperty = ClassProperty::name('test')->withDocBlock('This is a doc block.'); 326 | 327 | 328 | $printed = ClassTemplate::name('TestClass')->addProperty($classProperty)->print(); 329 | 330 | 331 | $this->assertStringContainsString(" /**\n * This is a doc block.\n */", $printed); 332 | } 333 | 334 | /** @test */ 335 | public function user_can_set_doc_block_using_fluent_method() 336 | { 337 | $classProperty = ClassProperty::name('test')->docBlock('This is a doc block.'); 338 | 339 | 340 | $printed = ClassTemplate::name('TestClass')->addProperty($classProperty)->print(); 341 | 342 | 343 | $this->assertStringContainsString("* This is a doc block.\n", $printed); 344 | } 345 | 346 | /** @test */ 347 | public function user_can_remove_doc_block_using_fluent_method() 348 | { 349 | $classProperty = ClassProperty::name('test')->docBlock('This is a doc block.'); 350 | 351 | 352 | $classProperty->docBlock(null); 353 | 354 | 355 | $this->assertNull($classProperty->getDocBlock()); 356 | } 357 | 358 | /** @test */ 359 | public function user_can_get_doc_block_from_class_property() 360 | { 361 | $classProperty = ClassProperty::name('test')->docBlock('This is a doc block.'); 362 | 363 | 364 | $docBlock = $classProperty->getDocBlock(); 365 | 366 | 367 | $this->assertEquals('This is a doc block.', $docBlock); 368 | } 369 | 370 | /** @test */ 371 | public function user_can_get_doc_block_using_fluent_method() 372 | { 373 | $classProperty = ClassProperty::name('test')->docBlock('This is a doc block.'); 374 | 375 | 376 | $docBlock = $classProperty->docBlock(); 377 | 378 | 379 | $this->assertEquals('This is a doc block.', $docBlock); 380 | } 381 | 382 | /** @test */ 383 | public function class_property_can_generate_default_doc_block_automatically() 384 | { 385 | $classProperty = ClassProperty::name('anotherTest')->type('bool'); 386 | 387 | 388 | $classProperty->withDefaultDocBlock(); 389 | $printed = ClassTemplate::name('TestClass')->addProperty($classProperty)->print(); 390 | 391 | 392 | $this->assertStringContainsString(" /**\n * @var bool \$anotherTest\n */", $printed); 393 | } 394 | } -------------------------------------------------------------------------------- /tests/Unit/ReferenceTest.php: -------------------------------------------------------------------------------- 1 | print(); 34 | 35 | 36 | $this->assertStringContainsString('$test', $printed); 37 | } 38 | 39 | /** @test */ 40 | public function user_can_create_variable_reference_using_reference_factory() 41 | { 42 | $variable = Reference::variable('test'); 43 | 44 | 45 | $printed = $variable->print(); 46 | 47 | 48 | $this->assertStringContainsString('$test', $printed); 49 | } 50 | 51 | /** @test */ 52 | public function user_can_create_variable_reference_from_argument() 53 | { 54 | $variable = Variable::fromArgument(Argument::name('test')); 55 | 56 | $this->assertInstanceOf(Variable::class, $variable); 57 | 58 | 59 | $printed = $variable->print(); 60 | 61 | 62 | $this->assertStringContainsString('$test', $printed); 63 | } 64 | 65 | /** @test */ 66 | public function user_can_create_this_reference_using_direct_constructor() 67 | { 68 | $thisReference = new This(); 69 | 70 | 71 | $printed = $thisReference->print(); 72 | 73 | 74 | $this->assertStringContainsString('$this', $printed); 75 | } 76 | 77 | /** @test */ 78 | public function user_can_create_this_reference_using_reference_factory() 79 | { 80 | $thisReference = Reference::this(); 81 | 82 | 83 | $printed = $thisReference->print(); 84 | 85 | 86 | $this->assertStringContainsString('$this', $printed); 87 | } 88 | 89 | /** @test */ 90 | public function user_can_create_object_property_reference_using_direct_constructor() 91 | { 92 | $objectProperty = new ObjectProperty(Variable::name('test'), 'first_attribute'); 93 | 94 | 95 | $printed = $objectProperty->print(); 96 | 97 | 98 | $this->assertStringContainsString('$test->first_attribute', $printed); 99 | } 100 | 101 | /** @test */ 102 | public function user_can_create_object_property_reference_using_reference_factory() 103 | { 104 | $objectProperty = Reference::objectProperty(Variable::name('user'), 'email'); 105 | 106 | 107 | $printed = $objectProperty->print(); 108 | 109 | 110 | $this->assertStringContainsString('$user->email', $printed); 111 | } 112 | 113 | /** 114 | * @test 115 | * @dataProvider objectContainersDataProvider 116 | */ 117 | public function user_can_create_object_property_using_any_object_container(ObjectContainer $object, string $printedObjectContainer) 118 | { 119 | $objectProperty = Reference::objectProperty($object, 'someProperty'); 120 | 121 | 122 | $printed = $objectProperty->print(); 123 | 124 | 125 | $this->assertStringContainsString("{$printedObjectContainer}->someProperty", $printed); 126 | } 127 | 128 | /** @test */ 129 | public function object_proprety_will_delegate_imports() 130 | { 131 | $loadInstanceBlock = Block::invokeStaticMethod( 132 | new Importable('Illuminate\Support\Facades\DB'), 133 | 'table', 134 | ['someTable'] 135 | )->chain('inRandomOrder')->chain('first'); 136 | 137 | $getKeyBlock = Reference::objectProperty( 138 | $loadInstanceBlock, 139 | 'someProperty' 140 | ); 141 | 142 | 143 | $printed = $getKeyBlock->print(); 144 | 145 | 146 | $this->assertStringContainsString('use Illuminate\Support\Facades\DB;', $printed); 147 | $this->assertStringContainsString("DB::table('someTable')->inRandomOrder()->first()->someProperty;", $printed); 148 | } 149 | 150 | /** @test */ 151 | public function user_can_create_static_property_reference_using_direct_constructor() 152 | { 153 | $staticProperty = new StaticProperty(Reference::classReference('App\Models\User'), 'totalCount'); 154 | 155 | 156 | $printed = $staticProperty->print(); 157 | 158 | 159 | $this->assertStringContainsString('App\Models\User::$totalCount', $printed); 160 | } 161 | 162 | /** @test */ 163 | public function user_can_create_static_property_reference_using_reference_factory() 164 | { 165 | $staticProperty = Reference::staticProperty('App\Models\Income', 'overallIncome'); 166 | 167 | 168 | $printed = $staticProperty->print(); 169 | 170 | 171 | $this->assertStringContainsString('App\Models\Income::$overallIncome', $printed); 172 | } 173 | 174 | /** @test */ 175 | public function user_can_create_static_property_reference_using_importable() 176 | { 177 | $staticProperty = Reference::staticProperty(new Importable('App\Models\Animals'), 'uniqueSpecies'); 178 | 179 | 180 | $printed = $staticProperty->print(); 181 | 182 | 183 | $this->assertStringContainsString('Animals::$uniqueSpecies', $printed); 184 | } 185 | 186 | /** @test */ 187 | public function user_can_create_static_property_reference_using_string_as_class_name() 188 | { 189 | $staticProperty = Reference::staticProperty('User', 'databaseConnection'); 190 | 191 | 192 | $printed = $staticProperty->print(); 193 | 194 | 195 | $this->assertStringContainsString('User::$databaseConnection;', $printed); 196 | } 197 | 198 | /** @test */ 199 | public function user_can_create_static_reference_using_direct_constructor() 200 | { 201 | $staticReference = new StaticReference(); 202 | 203 | 204 | $printed = $staticReference->print(); 205 | 206 | 207 | $this->assertStringContainsString('static::class', $printed); 208 | } 209 | 210 | /** @test */ 211 | public function user_can_create_static_reference_using_reference_factory() 212 | { 213 | $staticReference = Reference::staticReference(); 214 | 215 | 216 | $printed = $staticReference->print(); 217 | 218 | 219 | $this->assertStringContainsString('static::class', $printed); 220 | } 221 | 222 | /** @test */ 223 | public function user_can_create_self_reference_using_direct_constructor() 224 | { 225 | $selfReference = new SelfReference(); 226 | 227 | 228 | $printed = $selfReference->print(); 229 | 230 | 231 | $this->assertStringContainsString('self::class', $printed); 232 | } 233 | 234 | /** @test */ 235 | public function user_can_create_self_reference_using_reference_factory() 236 | { 237 | $selfReference = Reference::selfReference(); 238 | 239 | 240 | $printed = $selfReference->print(); 241 | 242 | 243 | $this->assertStringContainsString('self::class', $printed); 244 | } 245 | 246 | /** @test */ 247 | public function user_can_create_class_reference_using_direct_constructor() 248 | { 249 | $class = new ClassReference('Test\Unit\ReferenceTest'); 250 | 251 | 252 | $printed = $class->print(); 253 | 254 | 255 | $this->assertStringContainsString('Test\Unit\ReferenceTest::class', $printed); 256 | } 257 | 258 | /** @test */ 259 | public function user_can_create_class_reference_using_reference_factory() 260 | { 261 | $class = Reference::classReference('Test\Unit\ReferenceTest'); 262 | 263 | 264 | $printed = $class->print(); 265 | 266 | 267 | $this->assertStringContainsString('Test\Unit\ReferenceTest::class', $printed); 268 | } 269 | 270 | /** @test */ 271 | public function user_can_create_class_reference_using_importable() 272 | { 273 | $class = Reference::classReference(new Importable('Test\Unit\ReferenceTest')); 274 | 275 | 276 | $printed = $class->print(); 277 | 278 | 279 | $this->assertStringContainsString('ReferenceTest::class', $printed); 280 | 281 | $delegatedImports = $class->getDelegatedImports(); 282 | $this->assertCount(1, $delegatedImports); 283 | $this->assertEquals('Test\Unit\ReferenceTest', $delegatedImports['Test\Unit\ReferenceTest']->getName()); 284 | } 285 | 286 | public function validClassReferenceNormalizationValues() 287 | { 288 | return [ 289 | [new ClassReference('ClassName'), 'ClassName'], 290 | [new Importable('Some\Namespace\ClassName'), 'ClassName'], 291 | ['SomeClass', 'SomeClass'] 292 | ]; 293 | } 294 | 295 | /** 296 | * @test 297 | * @dataProvider validClassReferenceNormalizationValues 298 | */ 299 | public function values_can_be_normalized_to_class_reference($value, string $expectedName) 300 | { 301 | $normalized = ClassReference::normalize($value); 302 | 303 | 304 | $this->assertInstanceOf(ClassReference::class, $normalized); 305 | $this->assertEquals($expectedName, $normalized->getName()); 306 | } 307 | 308 | /** @test */ 309 | public function invalid_values_cannot_be_normalized_to_class_reference() 310 | { 311 | $invalidValue = 15; 312 | 313 | 314 | $this->expectException(\InvalidArgumentException::class); 315 | 316 | 317 | ClassReference::normalize($invalidValue); 318 | } 319 | 320 | /** @test */ 321 | public function user_can_create_array_key_reference_using_direct_instructor() 322 | { 323 | $arrayKeyReference = new ArrayKeyReference(Variable::name('test'), Value::string('test-key')); 324 | 325 | 326 | $printed = $arrayKeyReference->print(); 327 | 328 | 329 | $this->assertStringContainsString("\$test['test-key'];", $printed); 330 | } 331 | 332 | /** @test */ 333 | public function user_can_create_array_key_reference_using_factory_method() 334 | { 335 | $arrayKeyReference = Reference::arrayFetch(Block::invokeFunction('getArray'), 'test'); 336 | 337 | 338 | $printed = $arrayKeyReference->print(); 339 | 340 | 341 | $this->assertStringContainsString("getArray()['test'];", $printed); 342 | } 343 | 344 | /** 345 | * @test 346 | * @dataProvider arrayablesDataProvider 347 | */ 348 | public function user_can_create_array_key_reference_using_any_arrayable(Arrayable $array, string $printedArray) 349 | { 350 | $arrayKeyReference = Reference::arrayFetch($array, 'test'); 351 | 352 | 353 | $printed = $arrayKeyReference->print(); 354 | 355 | 356 | $this->assertStringContainsString("{$printedArray}['test'];", $printed); 357 | } 358 | 359 | /** 360 | * @test 361 | * @dataProvider assignableValuesDataProvider 362 | */ 363 | public function user_can_create_array_key_reference_using_any_assignable_value($key, string $printedKey) 364 | { 365 | $arrayKeyReference = Reference::arrayFetch(Reference::variable('test'), $key); 366 | 367 | 368 | $printed = $arrayKeyReference->print(); 369 | 370 | 371 | $this->assertStringContainsString("\$test[{$printedKey}];", $printed); 372 | } 373 | 374 | /** @test */ 375 | public function user_can_create_nested_array_key_reference() 376 | { 377 | $arrayKeyReference = Reference::arrayFetch(Reference::variable('test'), 'first-level'); 378 | 379 | 380 | $arrayKeyReference->nest('second-level', Block::invokeFunction('getThirdLevel'))->nest(Block::invokeStaticMethod('ArrayClass', 'getFourthLevel')); 381 | $printed = $arrayKeyReference->print(); 382 | 383 | 384 | $this->assertStringContainsString("\$test['first-level']['second-level'][getThirdLevel()][ArrayClass::getFourthLevel()];", $printed); 385 | } 386 | } -------------------------------------------------------------------------------- /tests/Unit/Imperative/InvokeTest.php: -------------------------------------------------------------------------------- 1 | print(); 33 | 34 | 35 | $this->assertStringContainsString("testFunction(15, \$test, \$anotherTest->doSomething('argument'))", $printed); 36 | } 37 | 38 | /** @test */ 39 | public function user_can_pass_in_importable_as_argument_to_invoked_function() 40 | { 41 | $invokeFunction = new InvokeFunctionBlock('testFunction', [ 42 | 'first-argument', 43 | Reference::classReference(new Importable('App\Models\User')), 44 | ]); 45 | 46 | 47 | $printed = $invokeFunction->print(); 48 | 49 | 50 | $this->assertStringContainsString('use App\Models\User;', $printed); 51 | $this->assertStringContainsString("testFunction('first-argument', User::class)", $printed); 52 | 53 | $imports = $invokeFunction->getDelegatedImports(); 54 | $this->assertCount(1, $imports); 55 | $this->assertEquals('App\Models\User', $imports['App\Models\User']->getName()); 56 | } 57 | 58 | /** @test */ 59 | public function user_can_create_the_invoke_function_block_using_block_factory() 60 | { 61 | $invokeFunction = Block::invokeFunction('doSomething', [ 62 | 'firstParameter', true 63 | ]); 64 | 65 | 66 | $printed = $invokeFunction->print(); 67 | 68 | 69 | $this->assertStringContainsString("doSomething('firstParameter', true);", $printed); 70 | } 71 | 72 | /** @test */ 73 | public function user_can_chain_method_calls_on_function_call() 74 | { 75 | $invokeFunction = Block::invokeFunction('findUser', [1]); 76 | 77 | 78 | $invokeFunction->chain('initialize')->chain('setUsername', ['testuser'])->chain('save'); 79 | $printed = $invokeFunction->print(); 80 | 81 | 82 | $this->assertStringContainsString("findUser(1)->initialize()->setUsername('testuser')->save();", $printed); 83 | } 84 | 85 | /** @test */ 86 | public function user_can_get_chained_method_using_fluent_alias_method() 87 | { 88 | $invokeFunction = Block::invokeFunction('findUser', [1]); 89 | $invokeFunction->chain('activate'); 90 | 91 | 92 | $chainedBlock = $invokeFunction->chain(); 93 | 94 | 95 | $this->assertInstanceOf(ChainedMethodBlock::class, $chainedBlock); 96 | } 97 | 98 | /** @test */ 99 | public function user_can_create_invoke_method_block_using_direct_constructor() 100 | { 101 | $invokeMethod = new InvokeMethodBlock(Variable::name('user'), 'promote', ['project-manager']); 102 | 103 | 104 | $printed = $invokeMethod->print(); 105 | 106 | 107 | $this->assertStringContainsString("\$user->promote('project-manager')", $printed); 108 | } 109 | 110 | /** @test */ 111 | public function user_can_create_invoke_method_block_using_block_factory() 112 | { 113 | $invokeMethod = Block::invokeMethod(new This(), 'isImportant'); 114 | 115 | 116 | $printed = $invokeMethod->print(); 117 | 118 | 119 | $this->assertStringContainsString('$this->isImportant()', $printed); 120 | } 121 | 122 | /** @test */ 123 | public function user_can_chain_method_calls_on_invoke_method_block() 124 | { 125 | $invokeMethod = Block::invokeMethod(Variable::name('user'), 'initialize'); 126 | 127 | 128 | $invokeMethod->chain('setUsername', ['nix224'])->chain('save')->chain('refresh'); 129 | $printed = $invokeMethod->print(); 130 | 131 | 132 | $this->assertStringContainsString("\$user->initialize()->setUsername('nix224')->save()->refresh();", $printed); 133 | } 134 | 135 | /** 136 | * @test 137 | * @dataProvider objectContainersDataProvider 138 | */ 139 | public function user_can_create_the_invoke_method_block_using_any_object_container_instance(ObjectContainer $object, string $printedObjectContainer) 140 | { 141 | $invokeMethod = Block::invokeMethod($object, 'doSomething'); 142 | 143 | 144 | $printed = $invokeMethod->print(); 145 | 146 | 147 | $this->assertStringContainsString("{$printedObjectContainer}->doSomething()", $printed); 148 | } 149 | 150 | /** @test */ 151 | public function user_can_create_the_invoke_static_method_block_using_direct_constructor() 152 | { 153 | $invokeStaticMethod = new InvokeStaticMethodBlock(Reference::classReference('App\Models\User'), 'query'); 154 | 155 | 156 | $printed = $invokeStaticMethod->print(); 157 | 158 | 159 | $this->assertStringContainsString('App\Models\User::query()', $printed); 160 | } 161 | 162 | /** @test */ 163 | public function user_can_create_the_invoke_static_method_class_using_importable() 164 | { 165 | $invokeStaticMethod = new InvokeStaticMethodBlock(Reference::classReference(new Importable('App\Models\User')), 'find', [15]); 166 | 167 | 168 | $printed = $invokeStaticMethod->print(); 169 | 170 | 171 | $this->assertStringContainsString('use App\Models\User;', $printed); 172 | $this->assertStringContainsString('User::find(15)', $printed); 173 | 174 | $imports = $invokeStaticMethod->getDelegatedImports(); 175 | $this->assertCount(1, $imports); 176 | $this->assertEquals('App\Models\User', $imports['App\Models\User']->getName()); 177 | } 178 | 179 | /** @test */ 180 | public function user_can_create_the_invoke_static_method_block_using_static_reference() 181 | { 182 | $invokeStaticMethod = Block::invokeStaticMethod(Reference::staticReference(), 'doSomething'); 183 | 184 | 185 | $printed = $invokeStaticMethod->print(); 186 | 187 | 188 | $this->assertStringContainsString('static::doSomething()', $printed); 189 | } 190 | 191 | /** @test */ 192 | public function user_can_create_the_invoke_static_method_block_using_self_reference() 193 | { 194 | $invokeStaticMethod = Block::invokeStaticMethod(Reference::selfReference(), 'doSomethingElse'); 195 | 196 | 197 | $printed = $invokeStaticMethod->print(); 198 | 199 | 200 | $this->assertStringContainsString('self::doSomethingElse()', $printed); 201 | } 202 | 203 | /** @test */ 204 | public function user_can_create_the_invoke_static_method_block_using_block_factory() 205 | { 206 | $invokeStaticMethod = Block::invokeStaticMethod(Reference::classReference('Car'), 'stopManufacturing', [ 207 | Block::invokeStaticMethod(Reference::classReference('CarFactory'), 'getMain'), 208 | ]); 209 | 210 | 211 | $printed = $invokeStaticMethod->print(); 212 | 213 | 214 | $this->assertStringContainsString('Car::stopManufacturing(CarFactory::getMain())', $printed); 215 | } 216 | 217 | /** @test */ 218 | public function user_can_create_the_invoke_static_method_block_using_string_as_class() 219 | { 220 | $invokeStaticMethod = Block::invokeStaticMethod('App\Models\User', 'doSomething'); 221 | 222 | 223 | $printed = $invokeStaticMethod->print(); 224 | 225 | 226 | $this->assertStringContainsString('App\Models\User::doSomething();', $printed); 227 | } 228 | 229 | /** @test */ 230 | public function user_can_create_the_invoke_static_method_block_using_importable_as_class() 231 | { 232 | $invokeStaticMethod = Block::invokeStaticMethod(new Importable('App\Models\User'), 'doSomething'); 233 | 234 | 235 | $printed = $invokeStaticMethod->print(); 236 | 237 | 238 | $this->assertStringContainsString('use App\Models\User;', $printed); 239 | $this->assertStringContainsString('User::doSomething();', $printed); 240 | } 241 | 242 | /** @test */ 243 | public function user_can_chain_method_calls_on_invoke_static_method_block() 244 | { 245 | $invokeStaticMethod = Block::invokeStaticMethod(Reference::classReference('User'), 'find', [1]); 246 | 247 | 248 | $invokeStaticMethod->chain('setActive', [true])->chain('setExpired', [false])->chain('notify'); 249 | $printed = $invokeStaticMethod->print(); 250 | 251 | 252 | $this->assertStringContainsString("User::find(1)->setActive(true)->setExpired(false)->notify();", $printed); 253 | } 254 | 255 | /** 256 | * @test 257 | * @dataProvider assignableValuesDataProvider 258 | */ 259 | public function user_can_pass_any_assignable_values_as_arguments_to_invoke_block($argument, string $expectedPrint) 260 | { 261 | $invokeBlock = Block::invokeMethod(Variable::name('test'), 'doSomething', [$argument]); 262 | 263 | 264 | $printed = $invokeBlock->print(); 265 | 266 | 267 | $this->assertStringContainsString("\$test->doSomething({$expectedPrint})", $printed); 268 | } 269 | 270 | /** @test */ 271 | public function user_can_invoke_with_no_arguments() 272 | { 273 | $invokeBlock = Block::invokeFunction('doSomething'); 274 | 275 | 276 | $printed = $invokeBlock->print(); 277 | 278 | 279 | $this->assertStringContainsString('doSomething()', $printed); 280 | } 281 | 282 | /** @test */ 283 | public function chained_calls_will_delegate_imports() 284 | { 285 | $invokeBlock = Block::invokeFunction('db'); 286 | 287 | 288 | $invokeBlock->chain('table', [ 289 | Block::invokeStaticMethod( 290 | Reference::classReference(new Importable('App\Models\Changelog')), 291 | 'getTable' 292 | ) 293 | ])->chain('where', [ 294 | 'changeable_type', 295 | Block::invokeStaticMethod( 296 | Reference::classReference(new Importable('App\Models\User')), 297 | 'getMorphableType' 298 | ) 299 | ])->chain('first'); 300 | $printed = $invokeBlock->print(); 301 | 302 | 303 | $this->assertStringContainsString('use App\Models\Changelog;', $printed); 304 | $this->assertStringContainsString('use App\Models\User;', $printed); 305 | $this->assertStringContainsString("db()->table(Changelog::getTable())->where('changeable_type', User::getMorphableType())->first();", $printed); 306 | } 307 | 308 | /** @test */ 309 | public function chained_method_blocks_will_delegate_printing_to_parent() 310 | { 311 | $parentBlock = $this->getMockBuilder(InvokeFunctionBlock::class)->disableOriginalConstructor()->getMock(); 312 | $chainedMethod = new ChainedMethodBlock($parentBlock, 'doSomething'); 313 | 314 | $parentBlock->expects($this->once())->method('print'); 315 | 316 | 317 | $chainedMethod->print(); 318 | } 319 | 320 | /** @test */ 321 | public function chained_method_blocks_will_delegate_imports() 322 | { 323 | $chainedInvocation = Block::invokeStaticMethod( 324 | new Importable('App\Services\SomeClass'), 325 | 'doSomething' 326 | )->chain('doSomethingElse')->chain('clean', [Reference::classReference(new Importable('App\Services\AnotherClass'))]); 327 | 328 | 329 | $printed = $chainedInvocation->print(); 330 | 331 | 332 | $this->assertStringContainsString('use App\Services\SomeClass;', $printed); 333 | $this->assertStringContainsString('use App\Services\AnotherClass;', $printed); 334 | 335 | $this->assertStringContainsString('SomeClass::doSomething()->doSomethingElse()->clean(AnotherClass::class);', $printed); 336 | } 337 | 338 | /** @test */ 339 | public function user_can_continue_chains_from_any_link_in_them() 340 | { 341 | $chain = Block::invokeFunction('doSomething'); 342 | $chain->chain('doSomethingElse')->chain('doAnotherThing'); 343 | 344 | 345 | $this->assertInstanceOf(InvokeFunctionBlock::class, $chain); 346 | $chain->continueChain('doOneMoreThing'); 347 | $printed = $chain->print(); 348 | 349 | 350 | $this->assertStringContainsString('doSomething()->doSomethingElse()->doAnotherThing()->doOneMoreThing();', $printed); 351 | } 352 | 353 | /** @test */ 354 | public function user_can_call_continue_chain_on_chain_root() 355 | { 356 | $chain = Block::invokeFunction('doSomething'); 357 | 358 | 359 | $chain->continueChain('doSomethingElse'); 360 | $printed = $chain->print(); 361 | 362 | 363 | $this->assertStringContainsString('doSomething()->doSomethingElse();', $printed); 364 | } 365 | } -------------------------------------------------------------------------------- /tests/Unit/Declarative/ClassMethodTest.php: -------------------------------------------------------------------------------- 1 | makeFinal(); 23 | 24 | $method->makeProtected(); 25 | 26 | $method->addArgument(Argument::name('firstParameter')); 27 | $method->addArgument(Argument::name('secondParameter')->type('string')); 28 | 29 | $method->return('bool'); 30 | 31 | 32 | $printed = $method->print(); 33 | 34 | 35 | $this->assertStringContainsString("protected final function doSomething(\$firstParameter, string \$secondParameter) : bool\n{\n}", $printed); 36 | } 37 | 38 | /** @test */ 39 | public function users_can_create_final_methods() 40 | { 41 | $method = ClassMethod::name('finalMethod')->makeFinal(); 42 | 43 | 44 | $printed = $method->print(); 45 | 46 | 47 | $this->assertStringContainsString("public final function finalMethod()\n{\n}", $printed); 48 | } 49 | 50 | /** @test */ 51 | public function users_can_create_final_methods_using_fluent_alias() 52 | { 53 | $method = ClassMethod::name('test')->final(true); 54 | 55 | 56 | $isFinal = $method->isFinal(); 57 | 58 | 59 | $this->assertTrue($isFinal); 60 | } 61 | 62 | /** @test */ 63 | public function users_can_check_if_method_is_final() 64 | { 65 | $method = ClassMethod::name('test'); 66 | 67 | 68 | $isFinal = $method->isFinal(); 69 | 70 | 71 | $this->assertFalse($isFinal); 72 | } 73 | 74 | /** @test */ 75 | public function user_can_check_if_method_is_final_using_fluent_alias() 76 | { 77 | $method = ClassMethod::name('test')->makeFinal(); 78 | 79 | 80 | $isFinal = $method->final(); 81 | 82 | 83 | $this->assertTrue($isFinal); 84 | } 85 | 86 | /** @test */ 87 | public function users_can_create_abstract_methods() 88 | { 89 | $method = ClassMethod::name('abstractMethod')->makeAbstract(); 90 | 91 | 92 | $printed = $method->print(); 93 | 94 | 95 | $this->assertStringContainsString("public abstract function abstractMethod();", $printed); 96 | } 97 | 98 | /** @test */ 99 | public function users_can_create_abstract_methods_using_fluent_alias() 100 | { 101 | $method = ClassMethod::name('test')->abstract(true); 102 | 103 | 104 | $isAbstract = $method->isAbstract(); 105 | 106 | 107 | $this->assertTrue($isAbstract); 108 | } 109 | 110 | /** @test */ 111 | public function users_can_check_if_method_is_abstract() 112 | { 113 | $method = ClassMethod::name('test'); 114 | 115 | 116 | $isAbstract = $method->isAbstract(); 117 | 118 | 119 | $this->assertFalse($isAbstract); 120 | } 121 | 122 | /** @test */ 123 | public function user_can_check_if_method_is_abstract_using_fluent_alias() 124 | { 125 | $method = ClassMethod::name('test')->makeAbstract(true); 126 | 127 | 128 | $isAbstract = $method->abstract(); 129 | 130 | 131 | $this->assertTrue($isAbstract); 132 | } 133 | 134 | /** @test */ 135 | public function users_can_create_public_methods() 136 | { 137 | $method = ClassMethod::name('publicMethod')->makePublic(); 138 | 139 | 140 | $printed = $method->print(); 141 | 142 | 143 | $this->assertStringContainsString("public function publicMethod()\n{\n}", $printed); 144 | } 145 | 146 | /** @test */ 147 | public function users_can_create_protected_methods() 148 | { 149 | $method = ClassMethod::name('protectedMethod')->makeProtected(); 150 | 151 | 152 | $printed = $method->print(); 153 | 154 | 155 | $this->assertStringContainsString("protected function protectedMethod()\n{\n}", $printed); 156 | } 157 | 158 | /** @test */ 159 | public function users_can_create_private_methods() 160 | { 161 | $method = ClassMethod::name('privateMethod')->makePrivate(); 162 | 163 | 164 | $printed = $method->print(); 165 | 166 | 167 | $this->assertStringContainsString("private function privateMethod()\n{\n}", $printed); 168 | } 169 | 170 | /** @test */ 171 | public function users_can_create_methods_with_importable_arguments() 172 | { 173 | $method = ClassMethod::name('methodWithImportableArgument'); 174 | $method->addArgument( 175 | Argument::name('object')->type(new Importable(\App\Models\User::class)) 176 | ); 177 | 178 | $class = ClassTemplate::name('TestClass')->addMethod($method)->setNamespace('Test'); 179 | 180 | 181 | $printed = $class->print(); 182 | 183 | 184 | $this->assertStringContainsString('use App\Models\User;', $printed); 185 | $this->assertStringContainsString("public function methodWithImportableArgument(User \$object)", $printed); 186 | } 187 | 188 | /** @test */ 189 | public function users_can_create_methods_without_arguments() 190 | { 191 | $method = ClassMethod::name('methodWithoutArguments'); 192 | 193 | 194 | $printed = $method->print(); 195 | 196 | 197 | $this->assertStringContainsString("public function methodWithoutArguments()\n{\n}", $printed); 198 | } 199 | 200 | /** @test */ 201 | public function users_can_override_all_method_arguments() 202 | { 203 | $method = ClassMethod::name('test'); 204 | 205 | $method->addArgument(Argument::name('test'))->addArgument(Argument::name('anotherTest')); 206 | 207 | 208 | $method->withArguments([Argument::name('iWillSurvive')]); 209 | $printed = $method->print(); 210 | 211 | 212 | $this->assertStringContainsString("public function test(\$iWillSurvive)\n{\n}", $printed); 213 | } 214 | 215 | /** @test */ 216 | public function users_cannot_override_all_method_arguments_using_invalid_array() 217 | { 218 | $method = ClassMethod::name('test'); 219 | 220 | $this->expectException(\InvalidArgumentException::class); 221 | 222 | 223 | $method->withArguments([ 224 | Argument::name('test'), 225 | 123, 226 | 'not a valid argument', 227 | ]); 228 | } 229 | 230 | /** @test */ 231 | public function users_can_remove_method_arguments_by_name() 232 | { 233 | $method = ClassMethod::name('test'); 234 | 235 | $method->addArgument(Argument::name('test'))->addArgument(Argument::name('anotherTest')); 236 | 237 | 238 | $method->removeArgument('anotherTest'); 239 | $printed = $method->print(); 240 | 241 | 242 | $this->assertStringContainsString("public function test(\$test)\n{\n}", $printed); 243 | } 244 | 245 | /** @test */ 246 | public function users_can_set_arguments_using_fluent_alias() 247 | { 248 | $method = ClassMethod::name('doSomething'); 249 | 250 | 251 | $method->arguments([ 252 | Argument::name('firstArgument')->type('string'), 253 | Argument::name('secondArgument')->type('bool'), 254 | ]); 255 | $printed = $method->print(); 256 | 257 | 258 | $this->assertStringContainsString('function doSomething(string $firstArgument, bool $secondArgument)', $printed); 259 | } 260 | 261 | /** @test */ 262 | public function users_can_get_all_arguments() 263 | { 264 | $method = ClassMethod::name('doSomething'); 265 | 266 | $method->arguments([ 267 | Argument::name('firstArgument')->type('string'), 268 | Argument::name('secondArgument')->type('bool'), 269 | ]); 270 | 271 | 272 | $arguments = $method->getArguments(); 273 | 274 | 275 | $this->assertIsArray($arguments); 276 | $this->assertCount(2, $arguments); 277 | 278 | $this->assertEquals('firstArgument', $arguments['firstArgument']->getName()); 279 | $this->assertEquals('string', $arguments['firstArgument']->getType()); 280 | 281 | $this->assertEquals('secondArgument', $arguments['secondArgument']->getName()); 282 | $this->assertEquals('bool', $arguments['secondArgument']->getType()); 283 | } 284 | 285 | /** @test */ 286 | public function users_can_get_all_arguments_using_fluent_alias() 287 | { 288 | $method = ClassMethod::name('doSomething'); 289 | 290 | $method->arguments([ 291 | Argument::name('firstArgument')->type('string'), 292 | Argument::name('secondArgument')->type('bool'), 293 | ]); 294 | 295 | 296 | $arguments = $method->arguments(); 297 | 298 | 299 | $this->assertIsArray($arguments); 300 | $this->assertCount(2, $arguments); 301 | 302 | $this->assertEquals('firstArgument', $arguments['firstArgument']->getName()); 303 | $this->assertEquals('string', $arguments['firstArgument']->getType()); 304 | 305 | $this->assertEquals('secondArgument', $arguments['secondArgument']->getName()); 306 | $this->assertEquals('bool', $arguments['secondArgument']->getType()); 307 | } 308 | 309 | /** @test */ 310 | public function users_can_create_methods_with_importable_return_types() 311 | { 312 | $method = ClassMethod::name('methodWithImportableReturnType'); 313 | 314 | $method->return(new Importable(\App\Models\Concert::class, 'ConcertModel')); 315 | 316 | $class = ClassTemplate::name('TestClass')->setNamespace('Test'); 317 | $class->addMethod($method); 318 | 319 | 320 | $printed = $class->print(); 321 | 322 | 323 | $this->assertStringContainsString('use App\Models\Concert as ConcertModel;', $printed); 324 | $this->assertStringContainsString("public function methodWithImportableReturnType() : ConcertModel\n", $printed); 325 | } 326 | 327 | /** @test */ 328 | public function users_can_create_methods_without_return_types() 329 | { 330 | $method = ClassMethod::name('methodWithoutReturnType'); 331 | 332 | 333 | $printed = $method->print(); 334 | 335 | 336 | $this->assertStringContainsString("public function methodWithoutReturnType()\n{\n}", $printed); 337 | } 338 | 339 | /** @test */ 340 | public function user_can_get_method_return_type() 341 | { 342 | $method = ClassMethod::name('doSomething')->setReturnType('bool'); 343 | 344 | 345 | $returnType = $method->getReturnType(); 346 | 347 | 348 | $this->assertEquals('bool', $returnType); 349 | } 350 | 351 | /** @test */ 352 | public function user_can_get_method_return_type_using_fluent_alias() 353 | { 354 | $method = ClassMethod::name('doSomething')->setReturnType('float'); 355 | 356 | 357 | $returnType = $method->return(); 358 | 359 | 360 | $this->assertEquals('float', $returnType); 361 | } 362 | 363 | /** @test */ 364 | public function user_can_make_methods_static() 365 | { 366 | $method = ClassMethod::name('staticMethodTest'); 367 | 368 | 369 | $method->makeStatic(); 370 | $printed = $method->print(); 371 | 372 | 373 | $this->assertStringContainsString('public static function staticMethodTest()', $printed); 374 | } 375 | 376 | /** @test */ 377 | public function user_can_make_methods_static_using_the_fluent_alias() 378 | { 379 | $method = ClassMethod::name('staticMethodTest'); 380 | 381 | 382 | $method->static(true); 383 | $printed = $method->print(); 384 | 385 | 386 | $this->assertStringContainsString('public static function staticMethodTest()', $printed); 387 | } 388 | 389 | /** 390 | * @test 391 | * @testWith [true] 392 | * [false] 393 | */ 394 | public function user_can_check_if_methods_are_static($shouldBeStatic) 395 | { 396 | $method = ClassMethod::name('staticMethod')->makeStatic($shouldBeStatic); 397 | 398 | 399 | $isStatic = $method->isStatic(); 400 | 401 | 402 | $this->assertEquals($shouldBeStatic, $isStatic); 403 | } 404 | 405 | /** 406 | * @test 407 | * @testWith [true] 408 | * [false] 409 | */ 410 | public function user_can_check_if_methods_are_static_using_the_fluent_alias($shouldBeStatic) 411 | { 412 | $method = ClassMethod::name('staticMethod')->makeStatic($shouldBeStatic); 413 | 414 | 415 | $isStatic = $method->static(); 416 | 417 | 418 | $this->assertEquals($shouldBeStatic, $isStatic); 419 | } 420 | 421 | /** @test */ 422 | public function user_can_set_blocks_as_method_body() 423 | { 424 | $method = ClassMethod::name('test'); 425 | 426 | 427 | $method->setBody(Block::fromArray([ 428 | Block::assign(Variable::name('test'), true) 429 | ])); 430 | $printed = $method->print(); 431 | 432 | 433 | $this->assertStringContainsString("public function test()\n{\n \$test = true;\n}", $printed); 434 | } 435 | 436 | /** @test */ 437 | public function user_can_get_method_body_block() 438 | { 439 | $method = ClassMethod::name('activate')->setBody( 440 | Block::fromArray([]) 441 | ); 442 | 443 | 444 | $body = $method->getBody(); 445 | 446 | 447 | $this->assertInstanceOf(Block::class, $body); 448 | } 449 | 450 | public function hasBodyDataProvider() 451 | { 452 | return [ 453 | 'Has body' => [new Block(), true], 454 | 'Does not have body' => [null, false], 455 | ]; 456 | } 457 | 458 | /** 459 | * @test 460 | * @dataProvider hasBodyDataProvider 461 | */ 462 | public function user_can_check_if_method_has_body($actualBody, bool $expectedHasBody) 463 | { 464 | $method = ClassMethod::name('test'); 465 | 466 | if ($actualBody) { 467 | $method->setBody($actualBody); 468 | } 469 | 470 | 471 | $actualHasBody = $method->hasBody(); 472 | 473 | 474 | $this->assertEquals($actualHasBody, $expectedHasBody); 475 | } 476 | 477 | /** @test */ 478 | public function user_can_set_method_body_using_fluent_alias() 479 | { 480 | $method = ClassMethod::name('activate'); 481 | 482 | 483 | $method->body(Block::fromArray([ 484 | Block::invokeMethod( 485 | new This(), 486 | 'setActive', 487 | [true] 488 | ) 489 | ])); 490 | $printed = $method->print(); 491 | 492 | 493 | $this->assertStringContainsString("public function activate()\n{\n \$this->setActive(true);\n}", $printed); 494 | } 495 | 496 | /** @test */ 497 | public function user_can_get_body_using_fluent_alias() 498 | { 499 | $method = ClassMethod::name('test')->setBody(Block::fromArray([])); 500 | 501 | 502 | $body = $method->body(); 503 | 504 | 505 | $this->assertInstanceOf(Block::class, $body); 506 | } 507 | 508 | /** @test */ 509 | public function body_will_delegate_imports_to_method() 510 | { 511 | $method = ClassMethod::name('test')->setBody( 512 | Block::fromArray([ 513 | Block::assign( 514 | Variable::name('user'), 515 | Block::instantiate(new Importable('App\Models\User')) 516 | ), 517 | Block::assign( 518 | Reference::objectProperty( 519 | Variable::name('user'), 520 | 'carMark', 521 | ), 522 | Reference::classReference(new Importable('App\Cars\BMW')), 523 | ), 524 | Block::return( 525 | Block::invokeStaticMethod( 526 | new Importable('App\Services\CarManufacturer'), 527 | 'queueManufacture', 528 | [ 529 | Reference::objectProperty(Variable::name('user'), 'carMark'), 530 | ] 531 | ) 532 | ) 533 | ]) 534 | ); 535 | 536 | 537 | /** @var \Shomisha\Stubless\ImperativeCode\UseStatement[] $imports */ 538 | $imports = $method->getDelegatedImports(); 539 | 540 | 541 | $this->assertCount(3, $imports); 542 | $this->assertEquals('App\Models\User', $imports['App\Models\User']->getName()); 543 | $this->assertEquals('App\Cars\BMW', $imports['App\Cars\BMW']->getName()); 544 | $this->assertEquals('App\Services\CarManufacturer', $imports['App\Services\CarManufacturer']->getName()); 545 | } 546 | 547 | /** @test */ 548 | public function class_methods_can_have_doc_blocks() 549 | { 550 | $method = ClassMethod::name('someMethod')->withDocBlock('This is a doc block'); 551 | 552 | 553 | $printed = ClassTemplate::name('TestClass')->addMethod($method)->print(); 554 | 555 | 556 | $this->assertStringContainsString(" /**\n * This is a doc block\n */\n", $printed); 557 | } 558 | 559 | /** @test */ 560 | public function user_can_set_doc_block_using_fluent_method() 561 | { 562 | $classMethod = ClassMethod::name('test')->docBlock('This is a doc block.'); 563 | 564 | 565 | $printed = ClassTemplate::name('TestClass')->addMethod($classMethod)->print(); 566 | 567 | 568 | $this->assertStringContainsString("* This is a doc block.\n", $printed); 569 | } 570 | 571 | /** @test */ 572 | public function user_can_remove_doc_block_using_fluent_method() 573 | { 574 | $classMethod = ClassMethod::name('test')->docBlock('This is a doc block.'); 575 | 576 | 577 | $classMethod->docBlock(null); 578 | 579 | 580 | $this->assertNull($classMethod->getDocBlock()); 581 | } 582 | 583 | /** @test */ 584 | public function user_can_get_doc_block_from_class_property() 585 | { 586 | $classMethod = ClassMethod::name('test')->docBlock('This is a doc block.'); 587 | 588 | 589 | $docBlock = $classMethod->getDocBlock(); 590 | 591 | 592 | $this->assertEquals('This is a doc block.', $docBlock); 593 | } 594 | 595 | /** @test */ 596 | public function user_can_get_doc_block_using_fluent_method() 597 | { 598 | $classMethod = ClassMethod::name('test')->docBlock('This is a doc block.'); 599 | 600 | 601 | $docBlock = $classMethod->docBlock(); 602 | 603 | 604 | $this->assertEquals('This is a doc block.', $docBlock); 605 | } 606 | 607 | /** @test */ 608 | public function class_methods_can_generate_default_doc_blocks_automatically() 609 | { 610 | $method = ClassMethod::name('someMethod')->return(new Importable('App\Models\User'))->withArguments([ 611 | Argument::name('test'), 612 | Argument::name('anotherTest')->type('string'), 613 | Argument::name('thirdTest')->type(new Importable('App\Models\ThirdTest', 'ThirdTestModel')) 614 | ]); 615 | 616 | 617 | $method->withDefaultDocBlock(); 618 | $printed = ClassTemplate::name('TestClass')->addMethod($method)->print(); 619 | 620 | 621 | $this->assertStringContainsString(" /**\n * @param \$test\n * @param string \$anotherTest\n * @param ThirdTestModel \$thirdTest\n * @return User\n */", $printed); 622 | } 623 | } --------------------------------------------------------------------------------