Skip to content

Commit

Permalink
Read discriminator mapping from file configuration (#2034)
Browse files Browse the repository at this point in the history
* Read discriminator mapping from file configuration

* Use more realistic test data
  • Loading branch information
EmilMassey committed Sep 25, 2022
1 parent 1842586 commit f808eaf
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 26 deletions.
2 changes: 1 addition & 1 deletion DependencyInjection/NelmioApiDocExtension.php
Expand Up @@ -144,7 +144,7 @@ public function load(array $configs, ContainerBuilder $container)
));

$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)
Expand Down
34 changes: 12 additions & 22 deletions ModelDescriber/ObjectModelDescriber.php
Expand Up @@ -23,7 +23,7 @@
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
Expand All @@ -33,6 +33,8 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar

/** @var PropertyInfoExtractorInterface */
private $propertyInfo;
/** @var ClassMetadataFactoryInterface */
private $classMetadataFactory;
/** @var Reader */
private $doctrineReader;
/** @var PropertyDescriberInterface[] */
Expand All @@ -46,13 +48,15 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar

public function __construct(
PropertyInfoExtractorInterface $propertyInfo,
ClassMetadataFactoryInterface $classMetadataFactory,
Reader $reader,
iterable $propertyDescribers,
array $mediaTypes,
NameConverterInterface $nameConverter = null,
bool $useValidationGroups = false
) {
$this->propertyInfo = $propertyInfo;
$this->classMetadataFactory = $classMetadataFactory;
$this->doctrineReader = $reader;
$this->propertyDescribers = $propertyDescribers;
$this->mediaTypes = $mediaTypes;
Expand Down Expand Up @@ -85,14 +89,17 @@ public function describe(Model $model, OA\Schema $schema)

$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()
);
}

Expand Down Expand Up @@ -196,23 +203,6 @@ private function describeProperty(array $types, Model $model, OA\Schema $propert
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()
Expand Down
1 change: 1 addition & 0 deletions Resources/config/services.xml
Expand Up @@ -71,6 +71,7 @@
<!-- Model Describers -->
<service id="nelmio_api_doc.model_describers.object" class="Nelmio\ApiDocBundle\ModelDescriber\ObjectModelDescriber" public="false">
<argument type="service" id="property_info" />
<argument type="service" id="serializer.mapping.class_metadata_factory" />
<argument type="service" id="annotations.reader" />
<argument type="tagged" tag="nelmio_api_doc.object_model.property_describer" />
<argument />
Expand Down
10 changes: 10 additions & 0 deletions Tests/Functional/Controller/ApiController80.php
Expand Up @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -277,6 +278,15 @@ public function discriminatorMappingAction()
{
}

/**
* @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"})
*
Expand Down
16 changes: 16 additions & 0 deletions Tests/Functional/Entity/SymfonyDiscriminatorFileMapping.php
@@ -0,0 +1,16 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;

interface SymfonyDiscriminatorFileMapping
{
}
2 changes: 1 addition & 1 deletion Tests/Functional/Entity/SymfonyDiscriminatorOne.php
Expand Up @@ -11,7 +11,7 @@

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;

class SymfonyDiscriminatorOne extends SymfonyDiscriminator
class SymfonyDiscriminatorOne extends SymfonyDiscriminator implements SymfonyDiscriminatorFileMapping
{
/**
* @var string
Expand Down
2 changes: 1 addition & 1 deletion Tests/Functional/Entity/SymfonyDiscriminatorTwo.php
Expand Up @@ -11,7 +11,7 @@

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;

class SymfonyDiscriminatorTwo extends SymfonyDiscriminator
class SymfonyDiscriminatorTwo extends SymfonyDiscriminator implements SymfonyDiscriminatorFileMapping
{
/**
* @var string
Expand Down
13 changes: 13 additions & 0 deletions Tests/Functional/FunctionalTest.php
Expand Up @@ -622,6 +622,19 @@ public function testModelsWithDiscriminatorMapAreLoadedWithOpenApiPolymorphism()
$this->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
Expand Down
6 changes: 6 additions & 0 deletions 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
7 changes: 6 additions & 1 deletion Tests/Functional/TestKernel.php
Expand Up @@ -131,7 +131,12 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load
'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
Expand Down

0 comments on commit f808eaf

Please sign in to comment.