From 85e06419439f100ddb681021ef9a0a3e1928fd82 Mon Sep 17 00:00:00 2001 From: DerManoMann Date: Fri, 28 Mar 2025 10:29:00 +1300 Subject: [PATCH 1/2] Introduce `Generator` settings Generator settings are following the same pattern as processor config and are stored under the key `'generator'`. `Generator::getSetting()` provides a shortcut to access generator settings. --- bin/openapi | 1 + src/Analysers/AnalyserInterface.php | 5 +--- src/Analysers/AnnotationFactoryInterface.php | 4 +-- src/Analysers/AttributeAnnotationFactory.php | 8 +++-- src/Analysers/DocBlockAnnotationFactory.php | 4 ++- src/Analysers/GeneratorAwareInterface.php | 14 +++++++++ src/Analysers/GeneratorAwareTrait.php | 4 ++- src/Analysers/ReflectionAnalyser.php | 4 ++- src/Generator.php | 17 ++++++++++- .../AttributeAnnotationFactoryTest.php | 30 +++++++++++++++++-- tests/Fixtures/PHP/Label.php | 4 +-- tests/Fixtures/UsingAttributes.php | 2 ++ tests/GeneratorTest.php | 11 +++++++ 13 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 src/Analysers/GeneratorAwareInterface.php diff --git a/bin/openapi b/bin/openapi index 4191205be..c302320d1 100755 --- a/bin/openapi +++ b/bin/openapi @@ -218,6 +218,7 @@ foreach ($options["processor"] as $processor) { } $analyser = new ReflectionAnalyser([new DocBlockAnnotationFactory(), new AttributeAnnotationFactory()]); +$analyser->setGenerator($generator); $openapi = $generator ->setVersion($options['version']) diff --git a/src/Analysers/AnalyserInterface.php b/src/Analysers/AnalyserInterface.php index 864d226a9..fa413e2c6 100644 --- a/src/Analysers/AnalyserInterface.php +++ b/src/Analysers/AnalyserInterface.php @@ -8,11 +8,8 @@ use OpenApi\Analysis; use OpenApi\Context; -use OpenApi\Generator; -interface AnalyserInterface +interface AnalyserInterface extends GeneratorAwareInterface { - public function setGenerator(Generator $generator): void; - public function fromFile(string $filename, Context $context): Analysis; } diff --git a/src/Analysers/AnnotationFactoryInterface.php b/src/Analysers/AnnotationFactoryInterface.php index 5c8365e62..404499295 100644 --- a/src/Analysers/AnnotationFactoryInterface.php +++ b/src/Analysers/AnnotationFactoryInterface.php @@ -10,14 +10,14 @@ use OpenApi\Context; use OpenApi\Generator; -interface AnnotationFactoryInterface +interface AnnotationFactoryInterface extends GeneratorAwareInterface { /** * Checks if this factory is supported by the current runtime. */ public function isSupported(): bool; - public function setGenerator(Generator $generator): void; + public function setGenerator(Generator $generator); /** * @return array top level annotations diff --git a/src/Analysers/AttributeAnnotationFactory.php b/src/Analysers/AttributeAnnotationFactory.php index 29f931d9a..4f37b0946 100644 --- a/src/Analysers/AttributeAnnotationFactory.php +++ b/src/Analysers/AttributeAnnotationFactory.php @@ -36,13 +36,17 @@ public function build(\Reflector $reflector, Context $context): array /** @var OA\AbstractAnnotation[] $annotations */ $annotations = []; try { - foreach ($reflector->getAttributes() as $attribute) { + $attributeArgs = $this->generator->getSetting('ignoreOtherAttributes') + ? [OA\AbstractAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF] + : []; + + foreach ($reflector->getAttributes(...$attributeArgs) as $attribute) { if (class_exists($attribute->getName())) { $instance = $attribute->newInstance(); if ($instance instanceof OA\AbstractAnnotation) { $annotations[] = $instance; } else { - if ($context->is('other') === false) { + if (false === $context->is('other')) { $context->other = []; } $context->other[] = $instance; diff --git a/src/Analysers/DocBlockAnnotationFactory.php b/src/Analysers/DocBlockAnnotationFactory.php index 469a10701..238448851 100644 --- a/src/Analysers/DocBlockAnnotationFactory.php +++ b/src/Analysers/DocBlockAnnotationFactory.php @@ -26,11 +26,13 @@ public function isSupported(): bool return DocBlockParser::isEnabled(); } - public function setGenerator(Generator $generator): void + public function setGenerator(Generator $generator) { $this->generator = $generator; $this->docBlockParser->setAliases($generator->getAliases()); + + return $this; } public function build(\Reflector $reflector, Context $context): array diff --git a/src/Analysers/GeneratorAwareInterface.php b/src/Analysers/GeneratorAwareInterface.php new file mode 100644 index 000000000..2985433cb --- /dev/null +++ b/src/Analysers/GeneratorAwareInterface.php @@ -0,0 +1,14 @@ +generator = $generator; + + return $this; } } diff --git a/src/Analysers/ReflectionAnalyser.php b/src/Analysers/ReflectionAnalyser.php index 9657e11c5..f49ab7678 100644 --- a/src/Analysers/ReflectionAnalyser.php +++ b/src/Analysers/ReflectionAnalyser.php @@ -42,13 +42,15 @@ public function __construct(array $annotationFactories = []) } } - public function setGenerator(Generator $generator): void + public function setGenerator(Generator $generator) { $this->generator = $generator; foreach ($this->annotationFactories as $annotationFactory) { $annotationFactory->setGenerator($generator); } + + return $this; } public function fromFile(string $filename, Context $context): Analysis diff --git a/src/Generator.php b/src/Generator.php index 153441d0d..187ca23f8 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -135,6 +135,9 @@ public function setAnalyser(?AnalyserInterface $analyser): Generator return $this; } + /** + * @deprecated + */ public function getDefaultConfig(): array { return [ @@ -146,7 +149,19 @@ public function getDefaultConfig(): array public function getConfig(): array { - return $this->config + $this->getDefaultConfig(); + return $this->config + $this->getDefaultConfig() + [ + 'generator' => [ + 'ignoreOtherAttributes' => false, + ], + ]; + } + + /** + * Get the value of a `Generator` setting. + */ + public function getSetting(string $name) + { + return $this->getConfig()['generator'][$name] ?? null; } protected function normaliseConfig(array $config): array diff --git a/tests/Analysers/AttributeAnnotationFactoryTest.php b/tests/Analysers/AttributeAnnotationFactoryTest.php index 711ee48ae..15b4d2d0c 100644 --- a/tests/Analysers/AttributeAnnotationFactoryTest.php +++ b/tests/Analysers/AttributeAnnotationFactoryTest.php @@ -7,6 +7,7 @@ namespace OpenApi\Tests\Analysers; use OpenApi\Analysers\AttributeAnnotationFactory; +use OpenApi\Generator; use OpenApi\Tests\Fixtures\UsingAttributes; use OpenApi\Tests\Fixtures\InvalidPropertyAttribute; use OpenApi\Tests\OpenApiTestCase; @@ -16,11 +17,22 @@ */ class AttributeAnnotationFactoryTest extends OpenApiTestCase { - public function testReturnedAnnotationsCout(): void + protected function getFactory(?array $config = null): AttributeAnnotationFactory + { + $generator = new Generator(); + if (null !== $config) { + $generator->setConfig($config); + } + + return (new AttributeAnnotationFactory()) + ->setGenerator($generator); + } + + public function testReturnedAnnotationsCount(): void { $rc = new \ReflectionClass(UsingAttributes::class); - $annotations = (new AttributeAnnotationFactory())->build($rc, $this->getContext()); + $annotations = $this->getFactory()->build($rc, $this->getContext()); $this->assertCount(1, $annotations); } @@ -32,6 +44,18 @@ public function testErrorOnInvalidAttribute(): void $this->expectException(\TypeError::class); $this->expectExceptionMessage('OpenApi\Attributes\Property::__construct(): Argument #8 ($required) must be of type ?array'); - (new AttributeAnnotationFactory())->build($rm, $this->getContext()); + $this->getFactory()->build($rm, $this->getContext()); + } + + public function testIgnoreOtherAttributes(): void + { + $rc = new \ReflectionClass(UsingAttributes::class); + + $this->getFactory()->build($rc, $context = $this->getContext()); + $this->assertIsArray($context->other); + $this->assertCount(1, $context->other); + + $this->getFactory(['generator' => ['ignoreOtherAttributes' => true]])->build($rc, $context = $this->getContext()); + $this->assertNull($context->other); } } diff --git a/tests/Fixtures/PHP/Label.php b/tests/Fixtures/PHP/Label.php index fedab46b9..bcc53d44a 100644 --- a/tests/Fixtures/PHP/Label.php +++ b/tests/Fixtures/PHP/Label.php @@ -6,12 +6,12 @@ namespace OpenApi\Tests\Fixtures\PHP; -#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class Label { protected $name; - public function __construct(string $name, array $numbers) + public function __construct(string $name, array $numbers = []) { $this->name = $name; } diff --git a/tests/Fixtures/UsingAttributes.php b/tests/Fixtures/UsingAttributes.php index 255a9f907..5481bf635 100644 --- a/tests/Fixtures/UsingAttributes.php +++ b/tests/Fixtures/UsingAttributes.php @@ -7,7 +7,9 @@ namespace OpenApi\Tests\Fixtures; use OpenApi\Attributes as OAT; +use OpenApi\Tests\Fixtures\PHP\Label; +#[Label(name: 'custom')] #[OAT\Response()] #[OAT\Header(header: 'X-Rate-Limit', allowEmptyValue: true)] class UsingAttributes diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php index 8ad940749..de5c01eb7 100644 --- a/tests/GeneratorTest.php +++ b/tests/GeneratorTest.php @@ -121,4 +121,15 @@ public function testConfig(array $config, bool $expected): void $generator->setConfig($config); $this->assertOperationIdHash($generator, $expected); } + + public function testGetSetting(): void + { + $generator = new Generator(); + + // valid; default + $this->assertFalse($generator->getSetting('ignoreOtherAttributes')); + + // invalid + $this->assertNull($generator->getSetting('invalid')); + } } From f970452f2ea8e154a61946794e51bd9871be674f Mon Sep 17 00:00:00 2001 From: DerManoMann Date: Thu, 3 Apr 2025 10:28:21 +1300 Subject: [PATCH 2/2] Give `ignoreOtherAttributes` a dedicated method --- src/Analysers/AttributeAnnotationFactory.php | 2 +- src/Generator.php | 16 +++++++++++----- tests/GeneratorTest.php | 9 ++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Analysers/AttributeAnnotationFactory.php b/src/Analysers/AttributeAnnotationFactory.php index 4f37b0946..3ecbbdacd 100644 --- a/src/Analysers/AttributeAnnotationFactory.php +++ b/src/Analysers/AttributeAnnotationFactory.php @@ -36,7 +36,7 @@ public function build(\Reflector $reflector, Context $context): array /** @var OA\AbstractAnnotation[] $annotations */ $annotations = []; try { - $attributeArgs = $this->generator->getSetting('ignoreOtherAttributes') + $attributeArgs = $this->generator->isIgnoreOtherAttributes() ? [OA\AbstractAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF] : []; diff --git a/src/Generator.php b/src/Generator.php index 187ca23f8..71d3f3fe7 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -21,6 +21,15 @@ * * This is an object-oriented alternative to using the now deprecated \OpenApi\scan() function and * static class properties of the Analyzer and Analysis classes. + * + * Supported generator config: + * + * [ + * 'generator' => [ + * 'ignoreOtherAttributes' => true|false, + * ] + * ] + * */ class Generator { @@ -156,12 +165,9 @@ public function getConfig(): array ]; } - /** - * Get the value of a `Generator` setting. - */ - public function getSetting(string $name) + public function isIgnoreOtherAttributes(): bool { - return $this->getConfig()['generator'][$name] ?? null; + return $this->getConfig()['generator']['ignoreOtherAttributes']; } protected function normaliseConfig(array $config): array diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php index de5c01eb7..3507da817 100644 --- a/tests/GeneratorTest.php +++ b/tests/GeneratorTest.php @@ -122,14 +122,13 @@ public function testConfig(array $config, bool $expected): void $this->assertOperationIdHash($generator, $expected); } - public function testGetSetting(): void + public function testIsIgnoreOtherAttributes(): void { $generator = new Generator(); - // valid; default - $this->assertFalse($generator->getSetting('ignoreOtherAttributes')); + $this->assertFalse($generator->isIgnoreOtherAttributes()); - // invalid - $this->assertNull($generator->getSetting('invalid')); + $generator->setConfig(['generator' => ['ignoreOtherAttributes' => true]]); + $this->assertTrue($generator->isIgnoreOtherAttributes()); } }