diff --git a/CHANGELOG.md b/CHANGELOG.md index 9440c2b44..7de0cf7a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ CHANGELOG ========= +4.25.0 +----- +* Added support for [JMS @Discriminator](https://jmsyst.com/libs/serializer/master/reference/annotations#discriminator) annotation/attribute + ```php + #[\JMS\Serializer\Annotation\Discriminator(field: 'type', map: ['car' => Car::class, 'plane' => Plane::class])] + abstract class Vehicle { } + class Car extends Vehicle { } + class Plane extends Vehicle { } + ``` + 4.24.0 ----- * Added support for some integer ranges (https://phpstan.org/writing-php-code/phpdoc-types#integer-ranges). diff --git a/src/ModelDescriber/JMSModelDescriber.php b/src/ModelDescriber/JMSModelDescriber.php index fc2191849..579b1145f 100644 --- a/src/ModelDescriber/JMSModelDescriber.php +++ b/src/ModelDescriber/JMSModelDescriber.php @@ -15,6 +15,7 @@ use JMS\Serializer\Context; use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface; use JMS\Serializer\Exclusion\GroupsExclusionStrategy; +use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Naming\PropertyNamingStrategyInterface; use JMS\Serializer\SerializationContext; use Metadata\MetadataFactoryInterface; @@ -33,6 +34,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareInterface { use ModelRegistryAwareTrait; + use ApplyOpenApiDiscriminatorTrait; private $factory; @@ -84,10 +86,25 @@ public function describe(Model $model, OA\Schema $schema) { $className = $model->getType()->getClassName(); $metadata = $this->factory->getMetadataForClass($className); - if (null === $metadata) { + if (!$metadata instanceof ClassMetadata) { throw new \InvalidArgumentException(sprintf('No metadata found for class %s.', $className)); } + if (!empty($metadata->discriminatorFieldName) + && $className === $metadata->discriminatorBaseClass + && [] !== $metadata->discriminatorMap + && Generator::UNDEFINED === $schema->discriminator) { + $this->applyOpenApiDiscriminator( + $model, + $schema, + $this->modelRegistry, + $metadata->discriminatorFieldName, + $metadata->discriminatorMap + ); + + return; + } + $annotationsReader = new AnnotationsReader( $this->doctrineReader, $this->modelRegistry, diff --git a/tests/Functional/Controller/JMSController80.php b/tests/Functional/Controller/JMSController80.php index 53ca9c2b3..0f9f57d8b 100644 --- a/tests/Functional/Controller/JMSController80.php +++ b/tests/Functional/Controller/JMSController80.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Controller; use Nelmio\ApiDocBundle\Annotation\Model; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\DiscriminatorMap\JMSAbstractUser; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSComplex80; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSDualComplex; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSNamingStrategyConstraints; @@ -151,4 +152,18 @@ public function minUserAction() public function minUserNestedAction() { } + + /** + * @Route("/api/jms_discriminator_map", methods={"GET"}) + * + * @OA\Response( + * response=200, + * description="Success", + * + * @Model(type=JMSAbstractUser::class) + * ) + */ + public function discriminatorMapAction() + { + } } diff --git a/tests/Functional/Controller/JMSController81.php b/tests/Functional/Controller/JMSController81.php index 6b0a81741..8848e196a 100644 --- a/tests/Functional/Controller/JMSController81.php +++ b/tests/Functional/Controller/JMSController81.php @@ -13,6 +13,7 @@ use Nelmio\ApiDocBundle\Annotation\Model; use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article81; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\DiscriminatorMap\JMSAbstractUser; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSComplex81; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSDualComplex; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSNamingStrategyConstraints; @@ -130,4 +131,14 @@ public function minUserNestedAction() public function enum() { } + + #[Route('/api/jms_discriminator_map', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Success', + content: new Model(type: JMSAbstractUser::class)) + ] + public function discriminatorMapAction() + { + } } diff --git a/tests/Functional/Entity/DiscriminatorMap/JMSAbstractUser.php b/tests/Functional/Entity/DiscriminatorMap/JMSAbstractUser.php new file mode 100644 index 000000000..f8038413b --- /dev/null +++ b/tests/Functional/Entity/DiscriminatorMap/JMSAbstractUser.php @@ -0,0 +1,41 @@ + JMSManager::class, 'administrator' => JMSAdministrator::class], groups: ['Default'])] + abstract class JMSAbstractUser + { + #[Serializer\Type('string')] + #[Serializer\Groups(['Default'])] + public $username; + } +} diff --git a/tests/Functional/Entity/DiscriminatorMap/JMSAdministrator.php b/tests/Functional/Entity/DiscriminatorMap/JMSAdministrator.php new file mode 100644 index 000000000..98c8f02f8 --- /dev/null +++ b/tests/Functional/Entity/DiscriminatorMap/JMSAdministrator.php @@ -0,0 +1,34 @@ +getModel('ArticleType81')->toJson(), true)); } + public function testModeDiscriminatorMap() + { + $this->assertEquals([ + 'type' => 'object', + 'properties' => [ + 'username' => [ + 'type' => 'string', + ], + ], + 'schema' => 'JMSManager', + ], json_decode($this->getModel('JMSManager')->toJson(), true)); + + $this->assertEquals([ + 'type' => 'object', + 'properties' => [ + 'username' => [ + 'type' => 'string', + ], + 'admin_title' => [ + 'type' => 'string', + ], + ], + 'schema' => 'JMSAdministrator', + ], json_decode($this->getModel('JMSAdministrator')->toJson(), true)); + + $this->assertEquals([ + 'oneOf' => [ + ['$ref' => '#/components/schemas/JMSManager'], + ['$ref' => '#/components/schemas/JMSAdministrator'], + ], + 'schema' => 'JMSAbstractUser', + 'discriminator' => [ + 'propertyName' => 'type', + 'mapping' => [ + 'manager' => '#/components/schemas/JMSManager', + 'administrator' => '#/components/schemas/JMSAdministrator', + ], + ], + ], json_decode($this->getModel('JMSAbstractUser')->toJson(), true)); + } + protected static function createKernel(array $options = []): KernelInterface { return new TestKernel(TestKernel::USE_JMS);