From 12ac91bfcd16898b18256dfe0cd248d4a02e971f Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 12 Mar 2021 00:59:35 +0100 Subject: [PATCH] Added support for constraint attributes --- .../SymfonyConstraintAnnotationReader.php | 29 +++++-- .../SymfonyConstraintAnnotationReaderTest.php | 76 +++++++++++++------ 2 files changed, 76 insertions(+), 29 deletions(-) diff --git a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php index 8b4e989..ba5f791 100644 --- a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php +++ b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php @@ -13,6 +13,7 @@ namespace Nelmio\ApiDocBundle\ModelDescriber\Annotations; use Doctrine\Common\Annotations\Reader; use EXSyst\Component\Swagger\Schema; +use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints as Assert; /** @@ -37,16 +38,12 @@ class SymfonyConstraintAnnotationReader /** * Update the given property and schema with defined Symfony constraints. + * + * @param \ReflectionProperty|\ReflectionMethod $reflection */ public function updateProperty($reflection, Schema $property) { - if ($reflection instanceof \ReflectionProperty) { - $annotations = $this->annotationsReader->getPropertyAnnotations($reflection); - } else { - $annotations = $this->annotationsReader->getMethodAnnotations($reflection); - } - - foreach ($annotations as $annotation) { + foreach ($this->getAnnotations($reflection) as $annotation) { if ($annotation instanceof Assert\NotBlank || $annotation instanceof Assert\NotNull) { // To support symfony/validator < 4.3 if ($annotation instanceof Assert\NotBlank && \property_exists($annotation, 'allowNull') && $annotation->allowNull) { @@ -128,4 +125,22 @@ class SymfonyConstraintAnnotationReader $property->setPattern($newPattern); } } + + /** + * @param \ReflectionProperty|\ReflectionMethod $reflection + */ + private function getAnnotations($reflection): \Traversable + { + if (\PHP_VERSION_ID >= 80000) { + foreach ($reflection->getAttributes(Constraint::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + yield $attribute->newInstance(); + } + } + + if ($reflection instanceof \ReflectionProperty) { + yield from $this->annotationsReader->getPropertyAnnotations($reflection); + } elseif ($reflection instanceof \ReflectionMethod) { + yield from $this->annotationsReader->getMethodAnnotations($reflection); + } + } } diff --git a/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php b/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php index 100173f..bc9ce5f 100644 --- a/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php +++ b/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php @@ -47,24 +47,16 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $this->assertEquals($schema->getRequired(), ['property1', 'property2']); } - public function testOptionalProperty() + /** + * @param object $entity + * @dataProvider provideOptionalProperty + */ + public function testOptionalProperty($entity) { if (!\property_exists(Assert\NotBlank::class, 'allowNull')) { $this->markTestSkipped('NotBlank::allowNull was added in symfony/validator 4.3.'); } - $entity = new class() { - /** - * @Assert\NotBlank(allowNull = true) - * @Assert\Length(min = 1) - */ - private $property1; - /** - * @Assert\NotBlank() - */ - private $property2; - }; - $schema = new Schema(); $schema->getProperties()->set('property1', new Schema()); $schema->getProperties()->set('property2', new Schema()); @@ -79,21 +71,37 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $this->assertEquals($schema->getRequired(), ['property2']); } - public function testAssertChoiceResultsInNumericArray() + public function provideOptionalProperty(): iterable { - define('TEST_ASSERT_CHOICE_STATUSES', [ - 1 => 'active', - 2 => 'blocked', - ]); - - $entity = new class() { + yield 'Annotations' => [new class() { /** + * @Assert\NotBlank(allowNull = true) * @Assert\Length(min = 1) - * @Assert\Choice(choices=TEST_ASSERT_CHOICE_STATUSES) */ private $property1; - }; + /** + * @Assert\NotBlank() + */ + private $property2; + }]; + if (\PHP_VERSION_ID >= 80000) { + yield 'Attributes' => [new class() { + #[Assert\NotBlank(allowNull: true)] + #[Assert\Length(min: 1)] + private $property1; + #[Assert\NotBlank] + private $property2; + }]; + } + } + + /** + * @param object $entity + * @dataProvider provideAssertChoiceResultsInNumericArray + */ + public function testAssertChoiceResultsInNumericArray($entity) + { $schema = new Schema(); $schema->getProperties()->set('property1', new Schema()); @@ -105,4 +113,28 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase // expect enum to be numeric array with sequential keys (not [1 => "active", 2 => "active"]) $this->assertEquals($schema->getProperties()->get('property1')->getEnum(), ['active', 'blocked']); } + + public function provideAssertChoiceResultsInNumericArray(): iterable + { + define('TEST_ASSERT_CHOICE_STATUSES', [ + 1 => 'active', + 2 => 'blocked', + ]); + + yield 'Annotations' => [new class() { + /** + * @Assert\Length(min = 1) + * @Assert\Choice(choices=TEST_ASSERT_CHOICE_STATUSES) + */ + private $property1; + }]; + + if (\PHP_VERSION_ID >= 80000) { + yield 'Attributes' => [new class() { + #[Assert\Length(min: 1)] + #[Assert\Choice(choices: TEST_ASSERT_CHOICE_STATUSES)] + private $property1; + }]; + } + } }