From 94ab27f5f323e895c0f9ef39a4f0c725c64b8215 Mon Sep 17 00:00:00 2001 From: jlabno Date: Fri, 12 Aug 2022 14:46:45 +0200 Subject: [PATCH 1/4] Add attributes generator (for classes only) --- src/Generator/AttributeGenerator.php | 54 +++++++++ .../AbstractAttributeAssembler.php | 24 ++++ .../AttributeGenerator/AttributeAssembler.php | 11 ++ .../AttributeAssemblerBag.php | 23 ++++ .../AttributeAssemblerFactory.php | 50 ++++++++ .../AttributeGenerator/AttributeBuilder.php | 29 +++++ .../AttributeGenerator/AttributePart.php | 18 +++ .../AttributeGenerator/AttributePrototype.php | 24 ++++ .../AttributeWithArgumentsAssembler.php | 47 ++++++++ ...stedAttributesAreNotSupportedException.php | 15 +++ ...uitableArgumentAssemlberFoundException.php | 11 ++ .../NotEmptyArgumentListException.php | 11 ++ .../SimpleAttributeAssembler.php | 34 ++++++ src/Generator/ClassGenerator.php | 23 ++++ .../AttributeGeneratorByBuilderTest.php | 109 ++++++++++++++++++ .../AttributeGeneratorByReflectionTest.php | 94 +++++++++++++++ test/Generator/ClassGeneratorTest.php | 53 +++++++++ .../AttributeWithArguments.php | 15 +++ .../ClassWithArgumentWithAttributes.php | 10 ++ .../ClassWithManyArgumentsWithAttributes.php | 11 ++ ...ClassWithSimpleAndArgumentedAttributes.php | 11 ++ .../ClassWithSimpleAttribute.php | 10 ++ .../ClassWithTwoSameSimpleAttributes.php | 11 ++ .../AttributeGenerator/SimpleAttribute.php | 12 ++ 24 files changed, 710 insertions(+) create mode 100644 src/Generator/AttributeGenerator.php create mode 100644 src/Generator/AttributeGenerator/AbstractAttributeAssembler.php create mode 100644 src/Generator/AttributeGenerator/AttributeAssembler.php create mode 100644 src/Generator/AttributeGenerator/AttributeAssemblerBag.php create mode 100644 src/Generator/AttributeGenerator/AttributeAssemblerFactory.php create mode 100644 src/Generator/AttributeGenerator/AttributeBuilder.php create mode 100644 src/Generator/AttributeGenerator/AttributePart.php create mode 100644 src/Generator/AttributeGenerator/AttributePrototype.php create mode 100644 src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php create mode 100644 src/Generator/AttributeGenerator/Exception/NestedAttributesAreNotSupportedException.php create mode 100644 src/Generator/AttributeGenerator/Exception/NoSuitableArgumentAssemlberFoundException.php create mode 100644 src/Generator/AttributeGenerator/Exception/NotEmptyArgumentListException.php create mode 100644 src/Generator/AttributeGenerator/SimpleAttributeAssembler.php create mode 100644 test/Generator/AttributeGeneratorByBuilderTest.php create mode 100644 test/Generator/AttributeGeneratorByReflectionTest.php create mode 100644 test/Generator/Fixture/AttributeGenerator/AttributeWithArguments.php create mode 100644 test/Generator/Fixture/AttributeGenerator/ClassWithArgumentWithAttributes.php create mode 100644 test/Generator/Fixture/AttributeGenerator/ClassWithManyArgumentsWithAttributes.php create mode 100644 test/Generator/Fixture/AttributeGenerator/ClassWithSimpleAndArgumentedAttributes.php create mode 100644 test/Generator/Fixture/AttributeGenerator/ClassWithSimpleAttribute.php create mode 100644 test/Generator/Fixture/AttributeGenerator/ClassWithTwoSameSimpleAttributes.php create mode 100644 test/Generator/Fixture/AttributeGenerator/SimpleAttribute.php diff --git a/src/Generator/AttributeGenerator.php b/src/Generator/AttributeGenerator.php new file mode 100644 index 00000000..bb7802d1 --- /dev/null +++ b/src/Generator/AttributeGenerator.php @@ -0,0 +1,54 @@ + $attributeAssembler->__toString(), + $this->attributeAssemblerBag->toArray(), + ); + + return implode(self::LINE_FEED, $generatedAttributes); + } + + public static function fromReflection(ReflectionClass $reflectionClass): self + { + return new self(AttributeAssemblerFactory::createForClassByReflection($reflectionClass)); + } + + public static function fromBuilder(AttributeBuilder $attributeBuilder): self + { + return new self(AttributeAssemblerFactory::createForClassFromBuilder($attributeBuilder)); + } + + public static function fromArray(array $definitions): self + { + $builder = new AttributeBuilder(); + + foreach ($definitions as $definition) { + @list($attributeName, $attributeArguments) = $definition; + + if (!isset($attributeArguments)) { + $attributeArguments = []; + } + + $builder->add($attributeName, $attributeArguments); + } + + return self::fromBuilder($builder); + } +} diff --git a/src/Generator/AttributeGenerator/AbstractAttributeAssembler.php b/src/Generator/AttributeGenerator/AbstractAttributeAssembler.php new file mode 100644 index 00000000..282b7274 --- /dev/null +++ b/src/Generator/AttributeGenerator/AbstractAttributeAssembler.php @@ -0,0 +1,24 @@ +attributePrototype->getName(); + } + + protected function getArguments(): array + { + return $this->attributePrototype->getArguments(); + } +} diff --git a/src/Generator/AttributeGenerator/AttributeAssembler.php b/src/Generator/AttributeGenerator/AttributeAssembler.php new file mode 100644 index 00000000..fffc6081 --- /dev/null +++ b/src/Generator/AttributeGenerator/AttributeAssembler.php @@ -0,0 +1,11 @@ +assemblers[] = $assembler; + } + + public function toArray(): array + { + return $this->assemblers; + } +} diff --git a/src/Generator/AttributeGenerator/AttributeAssemblerFactory.php b/src/Generator/AttributeGenerator/AttributeAssemblerFactory.php new file mode 100644 index 00000000..cd786045 --- /dev/null +++ b/src/Generator/AttributeGenerator/AttributeAssemblerFactory.php @@ -0,0 +1,50 @@ +getAttributes(); + $assemblyBag = new AttributeAssemblerBag(); + + foreach ($attributes as $attribute) { + $assembler = self::negotiateAssembler($attribute); + + $assemblyBag->add($assembler); + } + + return $assemblyBag; + } + + public static function createForClassFromBuilder(AttributeBuilder $attributeBuilder): AttributeAssemblerBag + { + $attributes = $attributeBuilder->build(); + $assemblyBag = new AttributeAssemblerBag(); + + foreach ($attributes as $attribute) { + $assembler = self::negotiateAssembler($attribute); + + $assemblyBag->add($assembler); + } + + return $assemblyBag; + } + + private static function negotiateAssembler(ReflectionAttribute|AttributePrototype $reflectionPrototype): AttributeAssembler + { + $hasArguments = !empty($reflectionPrototype->getArguments()); + + if ($hasArguments) { + return new AttributeWithArgumentsAssembler($reflectionPrototype); + } + + return new SimpleAttributeAssembler($reflectionPrototype); + } +} diff --git a/src/Generator/AttributeGenerator/AttributeBuilder.php b/src/Generator/AttributeGenerator/AttributeBuilder.php new file mode 100644 index 00000000..fae9d65e --- /dev/null +++ b/src/Generator/AttributeGenerator/AttributeBuilder.php @@ -0,0 +1,29 @@ +definitions[] = [$name, $arguments]; + + return $this; + } + + /** + * @return AttributePrototype[] + */ + public function build(): array + { + return array_map(function (array $definition): AttributePrototype { + list($name, $arguments) = $definition; + + return new AttributePrototype($name, $arguments); + }, $this->definitions); + } +} diff --git a/src/Generator/AttributeGenerator/AttributePart.php b/src/Generator/AttributeGenerator/AttributePart.php new file mode 100644 index 00000000..4b6f3ce2 --- /dev/null +++ b/src/Generator/AttributeGenerator/AttributePart.php @@ -0,0 +1,18 @@ +attributeName; + } + + public function getArguments(): array + { + return $this->arguments; + } +} diff --git a/src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php b/src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php new file mode 100644 index 00000000..f736cfb5 --- /dev/null +++ b/src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php @@ -0,0 +1,47 @@ +getName(); + + $attributeDefinition = AttributePart::T_ATTR_START . $attributeName . AttributePart::T_ATTR_ARGUMENTS_LIST_START; + + $this->generateArguments($attributeDefinition); + + return $attributeDefinition . AttributePart::T_ATTR_END; + } + + private function generateArguments(string &$output): void + { + $argumentsList = []; + + foreach ($this->getArguments() as $argumentName => $argumentValue) { + $argumentsList[] = $argumentName . ': ' . $this->formatArgumentValue($argumentValue); + } + + $output .= implode(AttributePart::T_ATTR_ARGUMENTS_LIST_SEPARATOR, $argumentsList); + $output .= AttributePart::T_ATTR_ARGUMENTS_LIST_END; + } + + private function formatArgumentValue(mixed $argument): mixed + { + switch (true) { + case is_string($argument): + return "'$argument'"; + case is_bool($argument): + return $argument ? 'true' : 'false'; + case is_array($argument): + throw NestedAttributesAreNotSupportedException::create(); + default: + return $argument; + } + } +} diff --git a/src/Generator/AttributeGenerator/Exception/NestedAttributesAreNotSupportedException.php b/src/Generator/AttributeGenerator/Exception/NestedAttributesAreNotSupportedException.php new file mode 100644 index 00000000..c823b5e9 --- /dev/null +++ b/src/Generator/AttributeGenerator/Exception/NestedAttributesAreNotSupportedException.php @@ -0,0 +1,15 @@ +assertAttributeWithoutArguments(); + } + + public function __toString(): string + { + $attributeName = $this->getName(); + + return AttributePart::T_ATTR_START . $attributeName . AttributePart::T_ATTR_END; + } + + private function assertAttributeWithoutArguments(): void + { + $arguments = $this->getArguments(); + + if (!empty($arguments)) { + throw new NotEmptyArgumentListException('Argument list has to be empty'); + } + } +} diff --git a/src/Generator/ClassGenerator.php b/src/Generator/ClassGenerator.php index c596d4ec..181c3378 100644 --- a/src/Generator/ClassGenerator.php +++ b/src/Generator/ClassGenerator.php @@ -2,6 +2,7 @@ namespace Laminas\Code\Generator; +use Laminas\Code\Generator\AttributeGenerator\AttributeBuilder; use Laminas\Code\Reflection\ClassReflection; use function array_diff; @@ -68,6 +69,8 @@ class ClassGenerator extends AbstractGenerator implements TraitUsageInterface /** @var TraitUsageGenerator Object to encapsulate trait usage logic */ protected TraitUsageGenerator $traitUsageGenerator; + private ?AttributeGenerator $attributeGenerator = null; + /** * Build a Code Generation Php Object from a Class Reflection * @@ -198,6 +201,10 @@ public static function fromArray(array $array) $docBlock = $value instanceof DocBlockGenerator ? $value : DocBlockGenerator::fromArray($value); $cg->setDocBlock($docBlock); break; + case 'attribute': + $generator = $value instanceof AttributeBuilder ? AttributeGenerator::fromBuilder($value) : AttributeGenerator::fromArray($value); + $cg->setAttributes($generator); + break; case 'flags': $cg->setFlags($value); break; @@ -336,6 +343,13 @@ public function setDocBlock(DocBlockGenerator $docBlock) return $this; } + public function setAttributes(AttributeGenerator $attributeGenerator): self + { + $this->attributeGenerator = $attributeGenerator; + + return $this; + } + /** * @return ?DocBlockGenerator */ @@ -344,6 +358,11 @@ public function getDocBlock() return $this->docBlock; } + public function getAttributes(): ?AttributeGenerator + { + return $this->attributeGenerator; + } + /** * @param int[]|int $flags * @return static @@ -1063,6 +1082,10 @@ public function generate() $output .= $docBlock->generate(); } + if ($attributeGenerator = $this->getAttributes()) { + $output .= $attributeGenerator->generate() . self::LINE_FEED; + } + if ($this->isAbstract()) { $output .= 'abstract '; } elseif ($this->isFinal()) { diff --git a/test/Generator/AttributeGeneratorByBuilderTest.php b/test/Generator/AttributeGeneratorByBuilderTest.php new file mode 100644 index 00000000..3e42ba2a --- /dev/null +++ b/test/Generator/AttributeGeneratorByBuilderTest.php @@ -0,0 +1,109 @@ +add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $generator = $this->giveGenerator($builder); + + $result = $generator->generate(); + + $expectedResult = '#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]'; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function generate_many_single_attributes(): void + { + $builder = new AttributeGenerator\AttributeBuilder(); + $builder->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $builder->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $generator = $this->giveGenerator($builder); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]\n#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]"; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function generate_single_attribute_with_arguments(): void + { + $builder = new AttributeGenerator\AttributeBuilder(); + $builder->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', [ + 'boolArgument' => false, + 'stringArgument' => 'char chain', + 'intArgument' => 16, + ]); + $generator = $this->giveGenerator($builder); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments(boolArgument: false, stringArgument: 'char chain', intArgument: 16)]"; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function generate_many_attributes_with_arguments(): void + { + $builder = new AttributeGenerator\AttributeBuilder(); + $builder + ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', [ + 'boolArgument' => false, + 'stringArgument' => 'char chain', + 'intArgument' => 16, + ]) + ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments'); + $generator = $this->giveGenerator($builder); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments(boolArgument: false, stringArgument: 'char chain', intArgument: 16)]\n#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments]"; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function mix_simple_attributes_with_attributes_with_arguments(): void + { + $builder = new AttributeGenerator\AttributeBuilder(); + $builder + ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', [ + 'stringArgument' => 'any string', + 'intArgument' => 1, + 'boolArgument' => true, + ]) + ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $generator = $this->giveGenerator($builder); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments(stringArgument: 'any string', intArgument: 1, boolArgument: true)]\n#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]"; + $this->assertSame($expectedResult, $result); + } + + private function giveGenerator(AttributeGenerator\AttributeBuilder $builder): AttributeGenerator + { + return AttributeGenerator::fromBuilder($builder); + } +} diff --git a/test/Generator/AttributeGeneratorByReflectionTest.php b/test/Generator/AttributeGeneratorByReflectionTest.php new file mode 100644 index 00000000..3b3a4e80 --- /dev/null +++ b/test/Generator/AttributeGeneratorByReflectionTest.php @@ -0,0 +1,94 @@ +giveGenerator($classWithSimpleAttribute); + + $result = $generator->generate(); + + $expectedResult = '#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]'; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function generate_many_single_attributes(): void + { + $classWithSimpleAttribute = new ClassWithTwoSameSimpleAttributes(); + $generator = $this->giveGenerator($classWithSimpleAttribute); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]\n#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]"; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function generate_single_attribute_with_arguments(): void + { + $classWithSimpleAttribute = new ClassWithArgumentWithAttributes(); + $generator = $this->giveGenerator($classWithSimpleAttribute); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments(boolArgument: false, stringArgument: 'char chain', intArgument: 16)]"; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function generate_many_attributes_with_arguments(): void + { + $classWithSimpleAttribute = new ClassWithManyArgumentsWithAttributes(); + $generator = $this->giveGenerator($classWithSimpleAttribute); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments(boolArgument: false, stringArgument: 'char chain', intArgument: 16)]\n#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments]"; + $this->assertSame($expectedResult, $result); + } + + /** + * @test + */ + public function mix_simple_attributes_with_attributes_with_arguments(): void + { + $classWithSimpleAttribute = new ClassWithSimpleAndArgumentedAttributes(); + $generator = $this->giveGenerator($classWithSimpleAttribute); + + $result = $generator->generate(); + + $expectedResult = "#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments(stringArgument: 'any string', intArgument: 1, boolArgument: true)]\n#[LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute]"; + $this->assertSame($expectedResult, $result); + } + + private function giveGenerator(object $class): AttributeGenerator + { + $reflection = new ReflectionClass($class); + + return AttributeGenerator::fromReflection($reflection); + } +} diff --git a/test/Generator/ClassGeneratorTest.php b/test/Generator/ClassGeneratorTest.php index e56f610c..f286ad6c 100644 --- a/test/Generator/ClassGeneratorTest.php +++ b/test/Generator/ClassGeneratorTest.php @@ -3,6 +3,7 @@ namespace LaminasTest\Code\Generator; use DateTime; +use Laminas\Code\Generator\AttributeGenerator; use Laminas\Code\Generator\ClassGenerator; use Laminas\Code\Generator\DocBlockGenerator; use Laminas\Code\Generator\Exception\ExceptionInterface; @@ -52,6 +53,14 @@ public function testClassDocBlockAccessors(): void self::assertSame($docBlockGenerator, $classGenerator->getDocBlock()); } + public function testClassAttributesAccessors(): void + { + $attributeGenerator = AttributeGenerator::fromBuilder(new AttributeGenerator\AttributeBuilder()); + $classGenerator = new ClassGenerator(); + $classGenerator->setAttributes($attributeGenerator); + self::assertSame($attributeGenerator, $classGenerator->getAttributes()); + } + public function testAbstractAccessors(): void { $classGenerator = new ClassGenerator(); @@ -516,6 +525,50 @@ public function testCreateFromArrayWithDocBlockFromArray(): void self::assertInstanceOf(DocBlockGenerator::class, $docBlock); } + public function testCreateFromArrayWithAttributesFromArray(): void + { + $attributeName = 'AnyAttribute'; + $attributeArguments = ['argument' => 2]; + + $classGenerator = ClassGenerator::fromArray([ + 'name' => 'AnyClassName', + 'attribute' => [ + [$attributeName, $attributeArguments], + ], + ]); + + $builder = (new AttributeGenerator\AttributeBuilder())->add($attributeName, $attributeArguments); + $expectedGenerator = AttributeGenerator::fromBuilder($builder); + $attributeGenerator = $classGenerator->getAttributes(); + self::assertInstanceOf(AttributeGenerator::class, $attributeGenerator); + self::assertEquals($expectedGenerator, $attributeGenerator); + } + + public function testGenerateAttributes(): void + { + $classGenerator = ClassGenerator::fromArray([ + 'name' => 'AnyClassName', + 'attribute' => [ + ['FirstAttribute', ['firstArgument' => 'abc', 'secondArgument' => 12]], + ['FirstAttribute', ['firstArgument' => 'abc', 'secondArgument' => 13]], + ['SecondAttribute'], + ], + ]); + + $generatedClass = $classGenerator->generate(); + + $expectedClassOutput = << Date: Fri, 12 Aug 2022 14:52:29 +0200 Subject: [PATCH 2/4] Add attributes generator (for classes only) --- .github/workflows/phpunit.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 62296047..70aab3e9 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -17,7 +17,6 @@ jobs: - "highest" - "locked" php-version: - - "7.4" - "8.0" - "8.1" operating-system: From e606216dff4ffa6badcea5a62a435bc73d757850 Mon Sep 17 00:00:00 2001 From: jlabno Date: Sun, 14 Aug 2022 16:59:50 +0200 Subject: [PATCH 3/4] https://github.com/laminas/laminas-code/pull/145 --- src/Generator/AttributeGenerator.php | 64 +++++++++++----- .../AttributeGenerator/AttributeAssembler.php | 5 +- .../AttributeAssemblerBag.php | 23 ------ .../AttributeAssemblerFactory.php | 50 ------------- .../AttributeGenerator/AttributeBuilder.php | 29 -------- .../AttributeGenerator/AttributePart.php | 5 +- .../AttributeWithArgumentsAssembler.php | 4 +- .../SimpleAttributeAssembler.php | 2 +- src/Generator/ClassGenerator.php | 11 ++- ... => AttributeGeneratorByPrototypeTest.php} | 73 ++++++++++--------- test/Generator/ClassGeneratorTest.php | 6 +- 11 files changed, 102 insertions(+), 170 deletions(-) delete mode 100644 src/Generator/AttributeGenerator/AttributeAssemblerBag.php delete mode 100644 src/Generator/AttributeGenerator/AttributeAssemblerFactory.php delete mode 100644 src/Generator/AttributeGenerator/AttributeBuilder.php rename test/Generator/{AttributeGeneratorByBuilderTest.php => AttributeGeneratorByPrototypeTest.php} (51%) diff --git a/src/Generator/AttributeGenerator.php b/src/Generator/AttributeGenerator.php index bb7802d1..685711b5 100644 --- a/src/Generator/AttributeGenerator.php +++ b/src/Generator/AttributeGenerator.php @@ -5,50 +5,78 @@ namespace Laminas\Code\Generator; use Laminas\Code\Generator\AttributeGenerator\AttributeAssembler; -use Laminas\Code\Generator\AttributeGenerator\AttributeAssemblerBag; -use Laminas\Code\Generator\AttributeGenerator\AttributeAssemblerFactory; -use Laminas\Code\Generator\AttributeGenerator\AttributeBuilder; +use Laminas\Code\Generator\AttributeGenerator\AttributePrototype; +use Laminas\Code\Generator\AttributeGenerator\AttributeWithArgumentsAssembler; +use Laminas\Code\Generator\AttributeGenerator\SimpleAttributeAssembler; +use ReflectionAttribute; use ReflectionClass; -class AttributeGenerator extends AbstractGenerator +final class AttributeGenerator implements GeneratorInterface { - private function __construct(private AttributeAssemblerBag $attributeAssemblerBag) + private array $assemblers; + + private function __construct(AttributeAssembler ...$assembler) { + $this->assemblers = $assembler; } public function generate(): string { - $generatedAttributes = array_map(fn(AttributeAssembler $attributeAssembler) => $attributeAssembler->__toString(), - $this->attributeAssemblerBag->toArray(), + $generatedAttributes = array_map(fn(AttributeAssembler $attributeAssembler) => $attributeAssembler->assemble(), + $this->assemblers, ); - return implode(self::LINE_FEED, $generatedAttributes); + return implode(AbstractGenerator::LINE_FEED, $generatedAttributes); } - public static function fromReflection(ReflectionClass $reflectionClass): self + public static function fromPrototype(AttributePrototype ...$attributePrototype): self { - return new self(AttributeAssemblerFactory::createForClassByReflection($reflectionClass)); + $assemblers = []; + + foreach ($attributePrototype as $prototype) { + $assemblers[] = self::negotiateAssembler($prototype); + } + + return new self(...$assemblers); } - public static function fromBuilder(AttributeBuilder $attributeBuilder): self + public static function fromReflection(ReflectionClass $reflectionClass): self { - return new self(AttributeAssemblerFactory::createForClassFromBuilder($attributeBuilder)); + $attributes = $reflectionClass->getAttributes(); + $assemblers = []; + + foreach ($attributes as $attribute) { + $assembler = self::negotiateAssembler($attribute); + + $assemblers[] = $assembler; + } + + return new self(...$assemblers); } public static function fromArray(array $definitions): self { - $builder = new AttributeBuilder(); + $assemblers = []; foreach ($definitions as $definition) { @list($attributeName, $attributeArguments) = $definition; - if (!isset($attributeArguments)) { - $attributeArguments = []; - } + $prototype = new AttributePrototype($attributeName, $attributeArguments ?? []); + + $assemblers[] = self::negotiateAssembler($prototype); + } + + return new self(...$assemblers); + } + + private static function negotiateAssembler(ReflectionAttribute|AttributePrototype $reflectionPrototype): AttributeAssembler + { + $hasArguments = !empty($reflectionPrototype->getArguments()); - $builder->add($attributeName, $attributeArguments); + if ($hasArguments) { + return new AttributeWithArgumentsAssembler($reflectionPrototype); } - return self::fromBuilder($builder); + return new SimpleAttributeAssembler($reflectionPrototype); } } diff --git a/src/Generator/AttributeGenerator/AttributeAssembler.php b/src/Generator/AttributeGenerator/AttributeAssembler.php index fffc6081..09d03684 100644 --- a/src/Generator/AttributeGenerator/AttributeAssembler.php +++ b/src/Generator/AttributeGenerator/AttributeAssembler.php @@ -4,8 +4,7 @@ namespace Laminas\Code\Generator\AttributeGenerator; -use Stringable; - -interface AttributeAssembler extends Stringable +interface AttributeAssembler { + public function assemble(): string; } diff --git a/src/Generator/AttributeGenerator/AttributeAssemblerBag.php b/src/Generator/AttributeGenerator/AttributeAssemblerBag.php deleted file mode 100644 index 77fd7c8f..00000000 --- a/src/Generator/AttributeGenerator/AttributeAssemblerBag.php +++ /dev/null @@ -1,23 +0,0 @@ -assemblers[] = $assembler; - } - - public function toArray(): array - { - return $this->assemblers; - } -} diff --git a/src/Generator/AttributeGenerator/AttributeAssemblerFactory.php b/src/Generator/AttributeGenerator/AttributeAssemblerFactory.php deleted file mode 100644 index cd786045..00000000 --- a/src/Generator/AttributeGenerator/AttributeAssemblerFactory.php +++ /dev/null @@ -1,50 +0,0 @@ -getAttributes(); - $assemblyBag = new AttributeAssemblerBag(); - - foreach ($attributes as $attribute) { - $assembler = self::negotiateAssembler($attribute); - - $assemblyBag->add($assembler); - } - - return $assemblyBag; - } - - public static function createForClassFromBuilder(AttributeBuilder $attributeBuilder): AttributeAssemblerBag - { - $attributes = $attributeBuilder->build(); - $assemblyBag = new AttributeAssemblerBag(); - - foreach ($attributes as $attribute) { - $assembler = self::negotiateAssembler($attribute); - - $assemblyBag->add($assembler); - } - - return $assemblyBag; - } - - private static function negotiateAssembler(ReflectionAttribute|AttributePrototype $reflectionPrototype): AttributeAssembler - { - $hasArguments = !empty($reflectionPrototype->getArguments()); - - if ($hasArguments) { - return new AttributeWithArgumentsAssembler($reflectionPrototype); - } - - return new SimpleAttributeAssembler($reflectionPrototype); - } -} diff --git a/src/Generator/AttributeGenerator/AttributeBuilder.php b/src/Generator/AttributeGenerator/AttributeBuilder.php deleted file mode 100644 index fae9d65e..00000000 --- a/src/Generator/AttributeGenerator/AttributeBuilder.php +++ /dev/null @@ -1,29 +0,0 @@ -definitions[] = [$name, $arguments]; - - return $this; - } - - /** - * @return AttributePrototype[] - */ - public function build(): array - { - return array_map(function (array $definition): AttributePrototype { - list($name, $arguments) = $definition; - - return new AttributePrototype($name, $arguments); - }, $this->definitions); - } -} diff --git a/src/Generator/AttributeGenerator/AttributePart.php b/src/Generator/AttributeGenerator/AttributePart.php index 4b6f3ce2..09cebd8b 100644 --- a/src/Generator/AttributeGenerator/AttributePart.php +++ b/src/Generator/AttributeGenerator/AttributePart.php @@ -5,6 +5,9 @@ namespace Laminas\Code\Generator\AttributeGenerator; //TODO Enum in PHP8.1 +/** + * @internal + */ final class AttributePart { public const T_ATTR_START = '#['; @@ -13,6 +16,6 @@ final class AttributePart public const T_ATTR_ARGUMENTS_LIST_START = '('; public const T_ATTR_ARGUMENTS_LIST_END = ')'; - public const T_ATTR_ARGUMENTS_LIST_ASSIGN_OPERAND = ':'; + public const T_ATTR_ARGUMENTS_LIST_ASSIGN_OPERAND = ': '; public const T_ATTR_ARGUMENTS_LIST_SEPARATOR = ', '; } diff --git a/src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php b/src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php index f736cfb5..b4db6a16 100644 --- a/src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php +++ b/src/Generator/AttributeGenerator/AttributeWithArgumentsAssembler.php @@ -8,7 +8,7 @@ final class AttributeWithArgumentsAssembler extends AbstractAttributeAssembler { - public function __toString(): string + public function assemble(): string { $attributeName = $this->getName(); @@ -24,7 +24,7 @@ private function generateArguments(string &$output): void $argumentsList = []; foreach ($this->getArguments() as $argumentName => $argumentValue) { - $argumentsList[] = $argumentName . ': ' . $this->formatArgumentValue($argumentValue); + $argumentsList[] = $argumentName . AttributePart::T_ATTR_ARGUMENTS_LIST_ASSIGN_OPERAND . $this->formatArgumentValue($argumentValue); } $output .= implode(AttributePart::T_ATTR_ARGUMENTS_LIST_SEPARATOR, $argumentsList); diff --git a/src/Generator/AttributeGenerator/SimpleAttributeAssembler.php b/src/Generator/AttributeGenerator/SimpleAttributeAssembler.php index 3059ed6a..aa88b952 100644 --- a/src/Generator/AttributeGenerator/SimpleAttributeAssembler.php +++ b/src/Generator/AttributeGenerator/SimpleAttributeAssembler.php @@ -16,7 +16,7 @@ public function __construct(ReflectionAttribute $attributePrototype) $this->assertAttributeWithoutArguments(); } - public function __toString(): string + public function assemble(): string { $attributeName = $this->getName(); diff --git a/src/Generator/ClassGenerator.php b/src/Generator/ClassGenerator.php index 181c3378..3821f265 100644 --- a/src/Generator/ClassGenerator.php +++ b/src/Generator/ClassGenerator.php @@ -202,7 +202,7 @@ public static function fromArray(array $array) $cg->setDocBlock($docBlock); break; case 'attribute': - $generator = $value instanceof AttributeBuilder ? AttributeGenerator::fromBuilder($value) : AttributeGenerator::fromArray($value); + $generator = $value instanceof AttributeGenerator ? $value : AttributeGenerator::fromArray($value); $cg->setAttributes($generator); break; case 'flags': @@ -235,17 +235,17 @@ public static function fromArray(array $array) * @psalm-param array $interfaces * @param PropertyGenerator[]|string[]|array[] $properties * @param MethodGenerator[]|string[]|array[] $methods - * @param DocBlockGenerator $docBlock */ public function __construct( - $name = null, + string $name = null, $namespaceName = null, $flags = null, $extends = null, array $interfaces = [], array $properties = [], array $methods = [], - $docBlock = null + DocBlockGenerator $docBlock = null, + AttributeGenerator $attributeGenerator = null, ) { $this->traitUsageGenerator = new TraitUsageGenerator($this); @@ -273,6 +273,9 @@ public function __construct( if ($docBlock !== null) { $this->setDocBlock($docBlock); } + if ($attributeGenerator) { + $this->setAttributes($attributeGenerator); + } } /** diff --git a/test/Generator/AttributeGeneratorByBuilderTest.php b/test/Generator/AttributeGeneratorByPrototypeTest.php similarity index 51% rename from test/Generator/AttributeGeneratorByBuilderTest.php rename to test/Generator/AttributeGeneratorByPrototypeTest.php index 3e42ba2a..c994f790 100644 --- a/test/Generator/AttributeGeneratorByBuilderTest.php +++ b/test/Generator/AttributeGeneratorByPrototypeTest.php @@ -5,19 +5,18 @@ namespace LaminasTest\Code\Generator; use Laminas\Code\Generator\AttributeGenerator; +use Laminas\Code\Generator\AttributeGenerator\AttributePrototype; use PHPUnit\Framework\TestCase; -use ReflectionClass; -final class AttributeGeneratorByBuilderTest extends TestCase +final class AttributeGeneratorByPrototypeTest extends TestCase { /** * @test */ public function generate_single_attribute(): void { - $builder = new AttributeGenerator\AttributeBuilder(); - $builder->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); - $generator = $this->giveGenerator($builder); + $prototype = new AttributePrototype('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $generator = $this->giveGenerator($prototype); $result = $generator->generate(); @@ -30,10 +29,9 @@ public function generate_single_attribute(): void */ public function generate_many_single_attributes(): void { - $builder = new AttributeGenerator\AttributeBuilder(); - $builder->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); - $builder->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); - $generator = $this->giveGenerator($builder); + $prototype1 = new AttributePrototype('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $prototype2 = new AttributePrototype('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $generator = $this->giveGenerator($prototype1, $prototype2); $result = $generator->generate(); @@ -46,13 +44,15 @@ public function generate_many_single_attributes(): void */ public function generate_single_attribute_with_arguments(): void { - $builder = new AttributeGenerator\AttributeBuilder(); - $builder->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', [ - 'boolArgument' => false, - 'stringArgument' => 'char chain', - 'intArgument' => 16, - ]); - $generator = $this->giveGenerator($builder); + $prototype = new AttributePrototype( + 'LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', + [ + 'boolArgument' => false, + 'stringArgument' => 'char chain', + 'intArgument' => 16, + ], + ); + $generator = $this->giveGenerator($prototype); $result = $generator->generate(); @@ -65,15 +65,16 @@ public function generate_single_attribute_with_arguments(): void */ public function generate_many_attributes_with_arguments(): void { - $builder = new AttributeGenerator\AttributeBuilder(); - $builder - ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', [ - 'boolArgument' => false, - 'stringArgument' => 'char chain', - 'intArgument' => 16, - ]) - ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments'); - $generator = $this->giveGenerator($builder); + $prototype1 = new AttributePrototype( + 'LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', + [ + 'boolArgument' => false, + 'stringArgument' => 'char chain', + 'intArgument' => 16, + ], + ); + $prototype2 = new AttributePrototype('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments'); + $generator = $this->giveGenerator($prototype1, $prototype2); $result = $generator->generate(); @@ -86,15 +87,15 @@ public function generate_many_attributes_with_arguments(): void */ public function mix_simple_attributes_with_attributes_with_arguments(): void { - $builder = new AttributeGenerator\AttributeBuilder(); - $builder - ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', [ - 'stringArgument' => 'any string', - 'intArgument' => 1, - 'boolArgument' => true, - ]) - ->add('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); - $generator = $this->giveGenerator($builder); + $prototype1 = new AttributePrototype( + 'LaminasTest\Code\Generator\Fixture\AttributeGenerator\AttributeWithArguments', + [ + 'stringArgument' => 'any string', + 'intArgument' => 1, + 'boolArgument' => true, + ]); + $prototype2 = new AttributePrototype('LaminasTest\Code\Generator\Fixture\AttributeGenerator\SimpleAttribute'); + $generator = $this->giveGenerator($prototype1, $prototype2); $result = $generator->generate(); @@ -102,8 +103,8 @@ public function mix_simple_attributes_with_attributes_with_arguments(): void $this->assertSame($expectedResult, $result); } - private function giveGenerator(AttributeGenerator\AttributeBuilder $builder): AttributeGenerator + private function giveGenerator(AttributePrototype ...$prototype): AttributeGenerator { - return AttributeGenerator::fromBuilder($builder); + return AttributeGenerator::fromPrototype(...$prototype); } } diff --git a/test/Generator/ClassGeneratorTest.php b/test/Generator/ClassGeneratorTest.php index f286ad6c..4f99d02c 100644 --- a/test/Generator/ClassGeneratorTest.php +++ b/test/Generator/ClassGeneratorTest.php @@ -4,6 +4,7 @@ use DateTime; use Laminas\Code\Generator\AttributeGenerator; +use Laminas\Code\Generator\AttributeGenerator\AttributePrototype; use Laminas\Code\Generator\ClassGenerator; use Laminas\Code\Generator\DocBlockGenerator; use Laminas\Code\Generator\Exception\ExceptionInterface; @@ -55,7 +56,7 @@ public function testClassDocBlockAccessors(): void public function testClassAttributesAccessors(): void { - $attributeGenerator = AttributeGenerator::fromBuilder(new AttributeGenerator\AttributeBuilder()); + $attributeGenerator = AttributeGenerator::fromArray([]); $classGenerator = new ClassGenerator(); $classGenerator->setAttributes($attributeGenerator); self::assertSame($attributeGenerator, $classGenerator->getAttributes()); @@ -537,8 +538,7 @@ public function testCreateFromArrayWithAttributesFromArray(): void ], ]); - $builder = (new AttributeGenerator\AttributeBuilder())->add($attributeName, $attributeArguments); - $expectedGenerator = AttributeGenerator::fromBuilder($builder); + $expectedGenerator = AttributeGenerator::fromPrototype(new AttributePrototype($attributeName, $attributeArguments)); $attributeGenerator = $classGenerator->getAttributes(); self::assertInstanceOf(AttributeGenerator::class, $attributeGenerator); self::assertEquals($expectedGenerator, $attributeGenerator); From 2e07cf3e2a6f2d859a6d859613a1ad9048e25672 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Tue, 13 Jun 2023 12:32:43 +0200 Subject: [PATCH 4/4] Add attributes generator (for classes only) --- .../AbstractAttributeAssembler.php | 4 ++-- .../AttributeGenerator/AttributeAssembler.php | 5 +++-- src/Generator/ClassGenerator.php | 8 ++++++-- src/Generator/ValueAssembler.php | 10 ++++++++++ test/Generator/ClassGeneratorTest.php | 20 +++++++++---------- 5 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 src/Generator/ValueAssembler.php diff --git a/src/Generator/AttributeGenerator/AbstractAttributeAssembler.php b/src/Generator/AttributeGenerator/AbstractAttributeAssembler.php index 282b7274..679a1fa0 100644 --- a/src/Generator/AttributeGenerator/AbstractAttributeAssembler.php +++ b/src/Generator/AttributeGenerator/AbstractAttributeAssembler.php @@ -12,12 +12,12 @@ public function __construct(private ReflectionAttribute $attributePrototype) { } - protected function getName(): string + final protected function getName(): string { return $this->attributePrototype->getName(); } - protected function getArguments(): array + final protected function getArguments(): array { return $this->attributePrototype->getArguments(); } diff --git a/src/Generator/AttributeGenerator/AttributeAssembler.php b/src/Generator/AttributeGenerator/AttributeAssembler.php index 09d03684..22eb48d9 100644 --- a/src/Generator/AttributeGenerator/AttributeAssembler.php +++ b/src/Generator/AttributeGenerator/AttributeAssembler.php @@ -4,7 +4,8 @@ namespace Laminas\Code\Generator\AttributeGenerator; -interface AttributeAssembler +use Laminas\Code\Generator\ValueAssembler; + +interface AttributeAssembler extends ValueAssembler { - public function assemble(): string; } diff --git a/src/Generator/ClassGenerator.php b/src/Generator/ClassGenerator.php index 3821f265..fef219bf 100644 --- a/src/Generator/ClassGenerator.php +++ b/src/Generator/ClassGenerator.php @@ -2,6 +2,7 @@ namespace Laminas\Code\Generator; +use InvalidArgumentException; use Laminas\Code\Generator\AttributeGenerator\AttributeBuilder; use Laminas\Code\Reflection\ClassReflection; @@ -202,8 +203,11 @@ public static function fromArray(array $array) $cg->setDocBlock($docBlock); break; case 'attribute': - $generator = $value instanceof AttributeGenerator ? $value : AttributeGenerator::fromArray($value); - $cg->setAttributes($generator); + if (!($value instanceof AttributeGenerator)) { + throw new InvalidArgumentException(sprintf('Only %s is supported', AttributeGenerator::class)); + } + + $cg->setAttributes($value); break; case 'flags': $cg->setFlags($value); diff --git a/src/Generator/ValueAssembler.php b/src/Generator/ValueAssembler.php new file mode 100644 index 00000000..495e0e36 --- /dev/null +++ b/src/Generator/ValueAssembler.php @@ -0,0 +1,10 @@ + 2]; + $attributeGenerator = AttributeGenerator::fromPrototype(new AttributePrototype($attributeName, $attributeArguments)); $classGenerator = ClassGenerator::fromArray([ 'name' => 'AnyClassName', - 'attribute' => [ - [$attributeName, $attributeArguments], - ], + 'attribute' => $attributeGenerator, ]); - $expectedGenerator = AttributeGenerator::fromPrototype(new AttributePrototype($attributeName, $attributeArguments)); + $expectedGenerator = $attributeGenerator; $attributeGenerator = $classGenerator->getAttributes(); self::assertInstanceOf(AttributeGenerator::class, $attributeGenerator); self::assertEquals($expectedGenerator, $attributeGenerator); @@ -546,13 +545,14 @@ public function testCreateFromArrayWithAttributesFromArray(): void public function testGenerateAttributes(): void { + $attributeGenerator = AttributeGenerator::fromPrototype( + new AttributePrototype('FirstAttribute', ['firstArgument' => 'abc', 'secondArgument' => 12]), + new AttributePrototype('FirstAttribute', ['firstArgument' => 'abc', 'secondArgument' => 13]), + new AttributePrototype('SecondAttribute'), + ); $classGenerator = ClassGenerator::fromArray([ 'name' => 'AnyClassName', - 'attribute' => [ - ['FirstAttribute', ['firstArgument' => 'abc', 'secondArgument' => 12]], - ['FirstAttribute', ['firstArgument' => 'abc', 'secondArgument' => 13]], - ['SecondAttribute'], - ], + 'attribute' => $attributeGenerator, ]); $generatedClass = $classGenerator->generate();