mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-02 23:59:26 +03:00
Merge pull request #1776 from chrisguitarguy/discriminator_field
Support Polymorphism via the Symfony Discriminator Map
This commit is contained in:
commit
1b4437d88a
60
ModelDescriber/ApplyOpenApiDiscriminatorTrait.php
Normal file
60
ModelDescriber/ApplyOpenApiDiscriminatorTrait.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?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\ModelDescriber;
|
||||
|
||||
use Nelmio\ApiDocBundle\Model\Model;
|
||||
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
/**
|
||||
* Contains helper methods that add `discriminator` and `oneOf` values to
|
||||
* Open API schemas to support poly morphism.
|
||||
*
|
||||
* @see https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait ApplyOpenApiDiscriminatorTrait
|
||||
{
|
||||
/**
|
||||
* @param Model $model the model that's being described, This is used to pass groups and config
|
||||
* down to the children models in `oneOf`
|
||||
* @param OA\Schema $schema The Open API schema to which `oneOf` and `discriminator` properties
|
||||
* will be added
|
||||
* @param string $discriminatorProperty The property that determine which model will be unsierailized
|
||||
* @param array<string, string> $typeMap the map of $discriminatorProperty values to their
|
||||
* types
|
||||
*/
|
||||
protected function applyOpenApiDiscriminator(
|
||||
Model $model,
|
||||
OA\Schema $schema,
|
||||
ModelRegistry $modelRegistry,
|
||||
string $discriminatorProperty,
|
||||
array $typeMap
|
||||
): void {
|
||||
$schema->oneOf = [];
|
||||
$schema->discriminator = new OA\Discriminator([]);
|
||||
$schema->discriminator->propertyName = $discriminatorProperty;
|
||||
$schema->discriminator->mapping = [];
|
||||
foreach ($typeMap as $propertyValue => $className) {
|
||||
$oneOfSchema = new OA\Schema([]);
|
||||
$oneOfSchema->ref = $modelRegistry->register(new Model(
|
||||
new Type(Type::BUILTIN_TYPE_OBJECT, false, $className),
|
||||
$model->getGroups(),
|
||||
$model->getOptions()
|
||||
));
|
||||
$schema->oneOf[] = $oneOfSchema;
|
||||
$schema->discriminator->mapping[$propertyValue] = $oneOfSchema->ref;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,11 +22,13 @@ use Nelmio\ApiDocBundle\PropertyDescriber\PropertyDescriberInterface;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
|
||||
|
||||
class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwareInterface
|
||||
{
|
||||
use ModelRegistryAwareTrait;
|
||||
use ApplyOpenApiDiscriminatorTrait;
|
||||
|
||||
/** @var PropertyInfoExtractorInterface */
|
||||
private $propertyInfo;
|
||||
@ -71,6 +73,17 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
|
||||
$annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry, $this->mediaTypes);
|
||||
$annotationsReader->updateDefinition($reflClass, $schema);
|
||||
|
||||
$discriminatorMap = $this->doctrineReader->getClassAnnotation($reflClass, DiscriminatorMap::class);
|
||||
if ($discriminatorMap && OA\UNDEFINED === $schema->discriminator) {
|
||||
$this->applyOpenApiDiscriminator(
|
||||
$model,
|
||||
$schema,
|
||||
$this->modelRegistry,
|
||||
$discriminatorMap->getTypeProperty(),
|
||||
$discriminatorMap->getMapping()
|
||||
);
|
||||
}
|
||||
|
||||
$propertyInfoProperties = $this->propertyInfo->getProperties($class, $context);
|
||||
|
||||
if (null === $propertyInfoProperties) {
|
||||
|
@ -18,6 +18,7 @@ use Nelmio\ApiDocBundle\Annotation\Security;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\CompoundEntity;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Form\UserType;
|
||||
@ -222,4 +223,13 @@ class ApiController
|
||||
public function compoundEntityAction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/discriminator-mapping", methods={"GET", "POST"})
|
||||
*
|
||||
* @OA\Response(response=200, description="Worked well!", @Model(type=SymfonyDiscriminator::class))
|
||||
*/
|
||||
public function discriminatorMappingAction()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
28
Tests/Functional/Entity/SymfonyDiscriminator.php
Normal file
28
Tests/Functional/Entity/SymfonyDiscriminator.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
|
||||
/**
|
||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "one": SymfonyDiscriminatorOne::class,
|
||||
* "two": SymfonyDiscriminatorTwo::class,
|
||||
* })
|
||||
*/
|
||||
abstract class SymfonyDiscriminator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
}
|
20
Tests/Functional/Entity/SymfonyDiscriminatorOne.php
Normal file
20
Tests/Functional/Entity/SymfonyDiscriminatorOne.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
|
||||
class SymfonyDiscriminatorOne extends SymfonyDiscriminator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $one;
|
||||
}
|
20
Tests/Functional/Entity/SymfonyDiscriminatorTwo.php
Normal file
20
Tests/Functional/Entity/SymfonyDiscriminatorTwo.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
|
||||
class SymfonyDiscriminatorTwo extends SymfonyDiscriminator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $two;
|
||||
}
|
@ -499,4 +499,24 @@ class FunctionalTest extends WebTestCase
|
||||
$this->assertNotHasProperty('protectedField', $model);
|
||||
$this->assertNotHasProperty('protected', $model);
|
||||
}
|
||||
|
||||
public function testModelsWithDiscriminatorMapAreLoadedWithOpenApiPolymorphism()
|
||||
{
|
||||
$model = $this->getModel('SymfonyDiscriminator');
|
||||
|
||||
$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(OA\UNDEFINED, $model->oneOf);
|
||||
$this->assertCount(2, $model->oneOf);
|
||||
}
|
||||
|
||||
public function testDiscriminatorMapLoadsChildrenModels()
|
||||
{
|
||||
// get model does its own assertions
|
||||
$this->getModel('SymfonyDiscriminatorOne');
|
||||
$this->getModel('SymfonyDiscriminatorTwo');
|
||||
}
|
||||
}
|
||||
|
87
Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php
Normal file
87
Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?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\ModelDescriber;
|
||||
|
||||
use Nelmio\ApiDocBundle\Model\Model;
|
||||
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
||||
use Nelmio\ApiDocBundle\ModelDescriber\ApplyOpenApiDiscriminatorTrait;
|
||||
use OpenApi\Annotations as OA;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
class ApplyOpenApiDiscriminatorTraitTest extends TestCase
|
||||
{
|
||||
use ApplyOpenApiDiscriminatorTrait;
|
||||
|
||||
const GROUPS = ['test'];
|
||||
const OPTIONS = ['test' => 123];
|
||||
|
||||
private $schema;
|
||||
|
||||
private $model;
|
||||
|
||||
public function testApplyAddsDiscriminatorProperty()
|
||||
{
|
||||
$this->applyOpenApiDiscriminator($this->model, $this->schema, $this->modelRegistry, 'type', [
|
||||
'one' => 'FirstType',
|
||||
'two' => 'SecondType',
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(OA\Discriminator::class, $this->schema->discriminator);
|
||||
$this->assertSame('type', $this->schema->discriminator->propertyName);
|
||||
$this->assertArrayHasKey('one', $this->schema->discriminator->mapping);
|
||||
$this->assertSame(
|
||||
$this->modelRegistry->register($this->createModel('FirstType')),
|
||||
$this->schema->discriminator->mapping['one']
|
||||
);
|
||||
$this->assertArrayHasKey('two', $this->schema->discriminator->mapping);
|
||||
$this->assertSame(
|
||||
$this->modelRegistry->register($this->createModel('SecondType')),
|
||||
$this->schema->discriminator->mapping['two']
|
||||
);
|
||||
}
|
||||
|
||||
public function testApplyAddsOneOfFieldToSchema()
|
||||
{
|
||||
$this->applyOpenApiDiscriminator($this->model, $this->schema, $this->modelRegistry, 'type', [
|
||||
'one' => 'FirstType',
|
||||
'two' => 'SecondType',
|
||||
]);
|
||||
|
||||
$this->assertNotSame(OA\UNDEFINED, $this->schema->oneOf);
|
||||
$this->assertCount(2, $this->schema->oneOf);
|
||||
$this->assertSame(
|
||||
$this->modelRegistry->register($this->createModel('FirstType')),
|
||||
$this->schema->oneOf[0]->ref
|
||||
);
|
||||
$this->assertSame(
|
||||
$this->modelRegistry->register($this->createModel('SecondType')),
|
||||
$this->schema->oneOf[1]->ref
|
||||
);
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->schema = new OA\Schema([]);
|
||||
$this->model = $this->createModel(__CLASS__);
|
||||
$this->modelRegistry = new ModelRegistry([], new OA\OpenApi([]));
|
||||
}
|
||||
|
||||
private function createModel(string $className): Model
|
||||
{
|
||||
return new Model(
|
||||
new Type(Type::BUILTIN_TYPE_OBJECT, false, $className),
|
||||
self::GROUPS,
|
||||
self::OPTIONS
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user