From 8948d5418b217bcfb7478cb720e9121af6ce0107 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Sun, 12 Jul 2020 14:54:39 +0200 Subject: [PATCH] Add methods metadata support for models (#1678) * Add methods metadata support for models * fix cs --- .../Annotations/AnnotationsReader.php | 12 ++-- .../Annotations/PropertyPhpDocReader.php | 4 +- .../Annotations/SwgAnnotationsReader.php | 20 +++++-- .../SymfonyConstraintAnnotationReader.php | 10 +++- ModelDescriber/ObjectModelDescriber.php | 57 +++++++++++++++---- .../Functional/Entity/SymfonyConstraints.php | 2 + Tests/Functional/Entity/User.php | 3 +- Tests/Functional/FunctionalTest.php | 2 + 8 files changed, 80 insertions(+), 30 deletions(-) diff --git a/ModelDescriber/Annotations/AnnotationsReader.php b/ModelDescriber/Annotations/AnnotationsReader.php index 5c9f92c..2cf7cc1 100644 --- a/ModelDescriber/Annotations/AnnotationsReader.php +++ b/ModelDescriber/Annotations/AnnotationsReader.php @@ -43,15 +43,15 @@ class AnnotationsReader $this->symfonyConstraintAnnotationReader->setSchema($schema); } - public function getPropertyName(\ReflectionProperty $reflectionProperty, string $default): string + public function getPropertyName($reflection, string $default): string { - return $this->swgAnnotationsReader->getPropertyName($reflectionProperty, $default); + return $this->swgAnnotationsReader->getPropertyName($reflection, $default); } - public function updateProperty(\ReflectionProperty $reflectionProperty, Schema $property, array $serializationGroups = null) + public function updateProperty($reflection, Schema $property, array $serializationGroups = null) { - $this->phpDocReader->updateProperty($reflectionProperty, $property); - $this->swgAnnotationsReader->updateProperty($reflectionProperty, $property, $serializationGroups); - $this->symfonyConstraintAnnotationReader->updateProperty($reflectionProperty, $property); + $this->phpDocReader->updateProperty($reflection, $property); + $this->swgAnnotationsReader->updateProperty($reflection, $property, $serializationGroups); + $this->symfonyConstraintAnnotationReader->updateProperty($reflection, $property); } } diff --git a/ModelDescriber/Annotations/PropertyPhpDocReader.php b/ModelDescriber/Annotations/PropertyPhpDocReader.php index ff823c7..8955539 100644 --- a/ModelDescriber/Annotations/PropertyPhpDocReader.php +++ b/ModelDescriber/Annotations/PropertyPhpDocReader.php @@ -33,10 +33,10 @@ class PropertyPhpDocReader /** * Update the Swagger information with information from the DocBlock comment. */ - public function updateProperty(\ReflectionProperty $reflectionProperty, Schema $property) + public function updateProperty($reflection, Schema $property) { try { - $docBlock = $this->docBlockFactory->create($reflectionProperty); + $docBlock = $this->docBlockFactory->create($reflection); } catch (\Exception $e) { // ignore return; diff --git a/ModelDescriber/Annotations/SwgAnnotationsReader.php b/ModelDescriber/Annotations/SwgAnnotationsReader.php index 9d365df..579b692 100644 --- a/ModelDescriber/Annotations/SwgAnnotationsReader.php +++ b/ModelDescriber/Annotations/SwgAnnotationsReader.php @@ -52,27 +52,35 @@ class SwgAnnotationsReader $schema->merge(json_decode(json_encode($swgDefinition))); } - public function getPropertyName(\ReflectionProperty $reflectionProperty, string $default): string + public function getPropertyName($reflection, string $default): string { /** @var SwgProperty $swgProperty */ - if (!$swgProperty = $this->annotationsReader->getPropertyAnnotation($reflectionProperty, SwgProperty::class)) { + if ($reflection instanceof \ReflectionProperty && !$swgProperty = $this->annotationsReader->getPropertyAnnotation($reflection, SwgProperty::class)) { + return $default; + } elseif ($reflection instanceof \ReflectionMethod && !$swgProperty = $this->annotationsReader->getMethodAnnotation($reflection, SwgProperty::class)) { return $default; } return $swgProperty->property ?? $default; } - public function updateProperty(\ReflectionProperty $reflectionProperty, Schema $property, array $serializationGroups = null) + public function updateProperty($reflection, Schema $property, array $serializationGroups = null) { - if (!$swgProperty = $this->annotationsReader->getPropertyAnnotation($reflectionProperty, SwgProperty::class)) { + if ($reflection instanceof \ReflectionProperty) { + $swgProperty = $this->annotationsReader->getPropertyAnnotation($reflection, SwgProperty::class); + } else { + $swgProperty = $this->annotationsReader->getMethodAnnotation($reflection, SwgProperty::class); + } + + if (!$swgProperty) { return; } - $declaringClass = $reflectionProperty->getDeclaringClass(); + $declaringClass = $reflection->getDeclaringClass(); $context = new Context([ 'namespace' => $declaringClass->getNamespaceName(), 'class' => $declaringClass->getShortName(), - 'property' => $reflectionProperty->name, + 'property' => $reflection->name, 'filename' => $declaringClass->getFileName(), ]); $swgProperty->_context = $context; diff --git a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php index f944ee0..6143672 100644 --- a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php +++ b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php @@ -38,9 +38,13 @@ class SymfonyConstraintAnnotationReader /** * Update the given property and schema with defined Symfony constraints. */ - public function updateProperty(\ReflectionProperty $reflectionProperty, Schema $property) + public function updateProperty($reflection, Schema $property) { - $annotations = $this->annotationsReader->getPropertyAnnotations($reflectionProperty); + if ($reflection instanceof \ReflectionProperty) { + $annotations = $this->annotationsReader->getPropertyAnnotations($reflection); + } else { + $annotations = $this->annotationsReader->getMethodAnnotations($reflection); + } foreach ($annotations as $annotation) { if ($annotation instanceof Assert\NotBlank || $annotation instanceof Assert\NotNull) { @@ -67,7 +71,7 @@ class SymfonyConstraintAnnotationReader $property->setMinItems($annotation->min); $property->setMaxItems($annotation->max); } elseif ($annotation instanceof Assert\Choice) { - $values = $annotation->callback ? call_user_func(is_array($annotation->callback) ? $annotation->callback : [$reflectionProperty->class, $annotation->callback]) : $annotation->choices; + $values = $annotation->callback ? call_user_func(is_array($annotation->callback) ? $annotation->callback : [$reflection->class, $annotation->callback]) : $annotation->choices; $property->setEnum(array_values($values)); } elseif ($annotation instanceof Assert\Range) { $property->setMinimum($annotation->min); diff --git a/ModelDescriber/ObjectModelDescriber.php b/ModelDescriber/ObjectModelDescriber.php index f361151..7a4a269 100644 --- a/ModelDescriber/ObjectModelDescriber.php +++ b/ModelDescriber/ObjectModelDescriber.php @@ -60,8 +60,10 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar $context['serializer_groups'] = array_filter($model->getGroups(), 'is_string'); } + $reflClass = new \ReflectionClass($class); + $annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry); - $annotationsReader->updateDefinition(new \ReflectionClass($class), $schema); + $annotationsReader->updateDefinition($reflClass, $schema); $propertyInfoProperties = $this->propertyInfo->getProperties($class, $context); if (null === $propertyInfoProperties) { @@ -71,19 +73,22 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar foreach ($propertyInfoProperties as $propertyName) { $serializedName = null !== $this->nameConverter ? $this->nameConverter->normalize($propertyName, $class, null, null !== $model->getGroups() ? ['groups' => $model->getGroups()] : []) : $propertyName; - // read property options from Swagger Property annotation if it exists - if (property_exists($class, $propertyName)) { - $reflectionProperty = new \ReflectionProperty($class, $propertyName); - $property = $properties->get($annotationsReader->getPropertyName($reflectionProperty, $serializedName)); + $reflections = $this->getReflections($reflClass, $propertyName); - $groups = $model->getGroups(); - if (isset($groups[$propertyName]) && is_array($groups[$propertyName])) { - $groups = $model->getGroups()[$propertyName]; - } + // Check if a custom name is set + foreach ($reflections as $reflection) { + $serializedName = $annotationsReader->getPropertyName($reflection, $serializedName); + } - $annotationsReader->updateProperty($reflectionProperty, $property, $groups); - } else { - $property = $properties->get($serializedName); + $property = $properties->get($serializedName); + + // Interpret additional options + $groups = $model->getGroups(); + if (isset($groups[$propertyName]) && is_array($groups[$propertyName])) { + $groups = $model->getGroups()[$propertyName]; + } + foreach ($reflections as $reflection) { + $annotationsReader->updateProperty($reflection, $property, $groups); } // If type manually defined @@ -104,6 +109,34 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar } } + /** + * @return \ReflectionProperty[]|\ReflectionMethod[] + */ + private function getReflections(\ReflectionClass $reflClass, string $propertyName): array + { + $reflections = []; + if ($reflClass->hasProperty($propertyName)) { + $reflections[] = $reflClass->getProperty($propertyName); + } + + $camelProp = $this->camelize($propertyName); + foreach (['', 'get', 'is', 'has', 'can', 'add', 'remove', 'set'] as $prefix) { + if ($reflClass->hasMethod($prefix.$camelProp)) { + $reflections[] = $reflClass->getMethod($prefix.$camelProp); + } + } + + return $reflections; + } + + /** + * Camelizes a given string. + */ + private function camelize(string $string): string + { + return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); + } + private function describeProperty(Type $type, Model $model, Schema $property, string $propertyName) { foreach ($this->propertyDescribers as $propertyDescriber) { diff --git a/Tests/Functional/Entity/SymfonyConstraints.php b/Tests/Functional/Entity/SymfonyConstraints.php index 6057d02..1e1bf6e 100644 --- a/Tests/Functional/Entity/SymfonyConstraints.php +++ b/Tests/Functional/Entity/SymfonyConstraints.php @@ -103,6 +103,8 @@ class SymfonyConstraints private $propertyLessThanOrEqual; /** + * @Assert\Count(min="0", max="10") + * * @param int $propertyNotBlank */ public function setPropertyNotBlank(int $propertyNotBlank): void diff --git a/Tests/Functional/Entity/User.php b/Tests/Functional/Entity/User.php index 6a1469b..aa41000 100644 --- a/Tests/Functional/Entity/User.php +++ b/Tests/Functional/Entity/User.php @@ -21,7 +21,7 @@ class User /** * @var int * - * @SWG\Property(description = "User id", readOnly = true, title = "userid", example=1, default = null) + * @SWG\Property(description = "User id", readOnly = true, title = "userid", default = null) */ private $id; @@ -93,6 +93,7 @@ class User /** * @param int $id + * @SWG\Property(example=1) */ public function setId(int $id) { diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index 8291c50..cd6e002 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -334,6 +334,8 @@ class FunctionalTest extends WebTestCase 'properties' => [ 'propertyNotBlank' => [ 'type' => 'integer', + 'maxItems' => '10', + 'minItems' => '0', ], 'propertyNotNull' => [ 'type' => 'integer',