From f808eafbe49f66180d7e73451a3c19fa4cf20b49 Mon Sep 17 00:00:00 2001 From: Emil Masiakowski Date: Sun, 25 Sep 2022 19:56:43 +0200 Subject: [PATCH] Read discriminator mapping from file configuration (#2034) * Read discriminator mapping from file configuration * Use more realistic test data --- DependencyInjection/NelmioApiDocExtension.php | 2 +- ModelDescriber/ObjectModelDescriber.php | 34 +++++++------------ Resources/config/services.xml | 1 + .../Functional/Controller/ApiController80.php | 10 ++++++ .../SymfonyDiscriminatorFileMapping.php | 16 +++++++++ .../Entity/SymfonyDiscriminatorOne.php | 2 +- .../Entity/SymfonyDiscriminatorTwo.php | 2 +- Tests/Functional/FunctionalTest.php | 13 +++++++ .../Resources/serializer/discriminator.yaml | 6 ++++ Tests/Functional/TestKernel.php | 7 +++- 10 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 Tests/Functional/Entity/SymfonyDiscriminatorFileMapping.php create mode 100644 Tests/Functional/Resources/serializer/discriminator.yaml diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index c394cf9..ce0d0e6 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -144,7 +144,7 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI )); $container->getDefinition('nelmio_api_doc.model_describers.object') - ->setArgument(3, $config['media_types']); + ->setArgument(4, $config['media_types']); // Add autoconfiguration for model describer $container->registerForAutoconfiguration(ModelDescriberInterface::class) diff --git a/ModelDescriber/ObjectModelDescriber.php b/ModelDescriber/ObjectModelDescriber.php index 368cb2c..8582ac5 100644 --- a/ModelDescriber/ObjectModelDescriber.php +++ b/ModelDescriber/ObjectModelDescriber.php @@ -23,7 +23,7 @@ use OpenApi\Annotations as OA; use OpenApi\Generator; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwareInterface @@ -33,6 +33,8 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar /** @var PropertyInfoExtractorInterface */ private $propertyInfo; + /** @var ClassMetadataFactoryInterface */ + private $classMetadataFactory; /** @var Reader */ private $doctrineReader; /** @var PropertyDescriberInterface[] */ @@ -46,6 +48,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar public function __construct( PropertyInfoExtractorInterface $propertyInfo, + ClassMetadataFactoryInterface $classMetadataFactory, Reader $reader, iterable $propertyDescribers, array $mediaTypes, @@ -53,6 +56,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar bool $useValidationGroups = false ) { $this->propertyInfo = $propertyInfo; + $this->classMetadataFactory = $classMetadataFactory; $this->doctrineReader = $reader; $this->propertyDescribers = $propertyDescribers; $this->mediaTypes = $mediaTypes; @@ -85,14 +89,17 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar $schema->type = 'object'; - $discriminatorMap = $this->getAnnotation($reflClass, DiscriminatorMap::class); - if ($discriminatorMap && Generator::UNDEFINED === $schema->discriminator) { + $mapping = $this->classMetadataFactory + ->getMetadataFor($class) + ->getClassDiscriminatorMapping(); + + if ($mapping && Generator::UNDEFINED === $schema->discriminator) { $this->applyOpenApiDiscriminator( $model, $schema, $this->modelRegistry, - $discriminatorMap->getTypeProperty(), - $discriminatorMap->getMapping() + $mapping->getTypeProperty(), + $mapping->getTypesMapping() ); } @@ -196,23 +203,6 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $types[0]->getBuiltinType(), $model->getType()->getClassName(), $propertyName)); } - /** - * @return mixed - */ - private function getAnnotation(\ReflectionClass $reflection, string $className) - { - if (false === class_exists($className)) { - return null; - } - if (\PHP_VERSION_ID >= 80000) { - if (null !== $attribute = $reflection->getAttributes($className, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) { - return $attribute->newInstance(); - } - } - - return $this->doctrineReader->getClassAnnotation($reflection, $className); - } - public function supports(Model $model): bool { return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() diff --git a/Resources/config/services.xml b/Resources/config/services.xml index b107302..9772302 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -71,6 +71,7 @@ + diff --git a/Tests/Functional/Controller/ApiController80.php b/Tests/Functional/Controller/ApiController80.php index 5d79a11..810e9d4 100644 --- a/Tests/Functional/Controller/ApiController80.php +++ b/Tests/Functional/Controller/ApiController80.php @@ -24,6 +24,7 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef; use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints; use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups; use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminatorFileMapping; use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType; use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithAlternateSchemaType; @@ -277,6 +278,15 @@ class ApiController80 { } + /** + * @Route("/discriminator-mapping-configured-with-file", methods={"GET", "POST"}) + * + * @OA\Response(response=200, description="Worked well!", @Model(type=SymfonyDiscriminatorFileMapping::class)) + */ + public function discriminatorMappingConfiguredWithFileAction() + { + } + /** * @Route("/named_route-operation-id", name="named_route_operation_id", methods={"GET", "POST"}) * diff --git a/Tests/Functional/Entity/SymfonyDiscriminatorFileMapping.php b/Tests/Functional/Entity/SymfonyDiscriminatorFileMapping.php new file mode 100644 index 0000000..ed80f48 --- /dev/null +++ b/Tests/Functional/Entity/SymfonyDiscriminatorFileMapping.php @@ -0,0 +1,16 @@ +assertCount(2, $model->oneOf); } + public function testModelsWithDiscriminatorMapAreLoadedWithOpenApiPolymorphismWhenUsingFileConfiguration() + { + $model = $this->getModel('SymfonyDiscriminatorFileMapping'); + + $this->assertInstanceOf(OA\Discriminator::class, $model->discriminator); + $this->assertSame('type', $model->discriminator->propertyName); + $this->assertCount(2, $model->discriminator->mapping); + $this->assertArrayHasKey('one', $model->discriminator->mapping); + $this->assertArrayHasKey('two', $model->discriminator->mapping); + $this->assertNotSame(Generator::UNDEFINED, $model->oneOf); + $this->assertCount(2, $model->oneOf); + } + public function testDiscriminatorMapLoadsChildrenModels() { // get model does its own assertions diff --git a/Tests/Functional/Resources/serializer/discriminator.yaml b/Tests/Functional/Resources/serializer/discriminator.yaml new file mode 100644 index 0000000..1a360f5 --- /dev/null +++ b/Tests/Functional/Resources/serializer/discriminator.yaml @@ -0,0 +1,6 @@ +Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminatorFileMapping: + discriminator_map: + type_property: type + mapping: + one: Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminatorOne + two: Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminatorTwo \ No newline at end of file diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php index 3f56348..f6ca42d 100644 --- a/Tests/Functional/TestKernel.php +++ b/Tests/Functional/TestKernel.php @@ -131,7 +131,12 @@ class TestKernel extends Kernel 'test' => null, 'validation' => null, 'form' => null, - 'serializer' => ['enable_annotations' => true], + 'serializer' => [ + 'enable_annotations' => true, + 'mapping' => [ + 'paths' => [__DIR__.'/Resources/serializer/'], + ], + ], 'property_access' => true, ]; // Support symfony/framework-bundle < 5.4