mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-03-12 10:36:09 +03:00
use jms serialization groups detection
This commit is contained in:
parent
ccad10aae1
commit
65e940f7f8
@ -13,6 +13,7 @@ namespace Nelmio\ApiDocBundle\ModelDescriber;
|
|||||||
|
|
||||||
use Doctrine\Common\Annotations\Reader;
|
use Doctrine\Common\Annotations\Reader;
|
||||||
use EXSyst\Component\Swagger\Schema;
|
use EXSyst\Component\Swagger\Schema;
|
||||||
|
use JMS\Serializer\Context;
|
||||||
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
|
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
|
||||||
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
|
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
|
||||||
use JMS\Serializer\SerializationContext;
|
use JMS\Serializer\SerializationContext;
|
||||||
@ -31,9 +32,14 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
use ModelRegistryAwareTrait;
|
use ModelRegistryAwareTrait;
|
||||||
|
|
||||||
private $factory;
|
private $factory;
|
||||||
|
|
||||||
private $namingStrategy;
|
private $namingStrategy;
|
||||||
|
|
||||||
private $doctrineReader;
|
private $doctrineReader;
|
||||||
private $previousGroups = [];
|
|
||||||
|
private $contexts = [];
|
||||||
|
|
||||||
|
private $metadataStacks = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
@ -61,40 +67,22 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
throw new \InvalidArgumentException(sprintf('No metadata found for class %s.', $className));
|
throw new \InvalidArgumentException(sprintf('No metadata found for class %s.', $className));
|
||||||
}
|
}
|
||||||
|
|
||||||
$groupsExclusion = null !== $model->getGroups() ? new GroupsExclusionStrategy($model->getGroups()) : null;
|
|
||||||
|
|
||||||
$schema->setType('object');
|
$schema->setType('object');
|
||||||
$annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry);
|
$annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry);
|
||||||
$annotationsReader->updateDefinition(new \ReflectionClass($className), $schema);
|
$annotationsReader->updateDefinition(new \ReflectionClass($className), $schema);
|
||||||
|
|
||||||
$isJmsV1 = null !== $this->namingStrategy;
|
$isJmsV1 = null !== $this->namingStrategy;
|
||||||
|
|
||||||
$properties = $schema->getProperties();
|
$properties = $schema->getProperties();
|
||||||
|
|
||||||
|
$context = $this->getSerializationContext($model);
|
||||||
|
$context->pushClassMetadata($metadata);
|
||||||
foreach ($metadata->propertyMetadata as $item) {
|
foreach ($metadata->propertyMetadata as $item) {
|
||||||
// filter groups
|
// filter groups
|
||||||
if (null !== $groupsExclusion && $groupsExclusion->shouldSkipProperty($item, SerializationContext::create())) {
|
if (null !== $context->getExclusionStrategy() && $context->getExclusionStrategy()->shouldSkipProperty($item, $context)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$groups = $model->getGroups();
|
$context->pushPropertyMetadata($item);
|
||||||
|
|
||||||
$previousGroups = null;
|
|
||||||
if (isset($groups[$item->name]) && is_array($groups[$item->name])) {
|
|
||||||
$previousGroups = $groups;
|
|
||||||
$groups = $groups[$item->name];
|
|
||||||
} elseif (!isset($groups[$item->name]) && !empty($this->previousGroups[$model->getHash()])) {
|
|
||||||
$groups = false === $this->propertyTypeUsesGroups($item->type)
|
|
||||||
? null
|
|
||||||
: ($isJmsV1 ? [GroupsExclusionStrategy::DEFAULT_GROUP] : $this->previousGroups[$model->getHash()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($groups)) {
|
|
||||||
$groups = array_filter($groups, 'is_scalar');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([GroupsExclusionStrategy::DEFAULT_GROUP] === $groups) {
|
|
||||||
$groups = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = true === $isJmsV1 ? $this->namingStrategy->translateName($item) : $item->serializedName;
|
$name = true === $isJmsV1 ? $this->namingStrategy->translateName($item) : $item->serializedName;
|
||||||
// read property options from Swagger Property annotation if it exists
|
// read property options from Swagger Property annotation if it exists
|
||||||
@ -106,22 +94,71 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
$property = $properties->get($annotationsReader->getPropertyName($reflection, $name));
|
$property = $properties->get($annotationsReader->getPropertyName($reflection, $name));
|
||||||
|
$groups = $this->computeGroups($context, $item->type);
|
||||||
$annotationsReader->updateProperty($reflection, $property, $groups);
|
$annotationsReader->updateProperty($reflection, $property, $groups);
|
||||||
} catch (\ReflectionException $e) {
|
} catch (\ReflectionException $e) {
|
||||||
$property = $properties->get($name);
|
$property = $properties->get($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $property->getType() || null !== $property->getRef()) {
|
if (null !== $property->getType() || null !== $property->getRef()) {
|
||||||
|
$context->popPropertyMetadata();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (null === $item->type) {
|
if (null === $item->type) {
|
||||||
$properties->remove($name);
|
$properties->remove($name);
|
||||||
|
$context->popPropertyMetadata();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->describeItem($item->type, $property, $groups, $previousGroups);
|
$this->describeItem($item->type, $property, $context, $item);
|
||||||
|
$context->popPropertyMetadata();
|
||||||
}
|
}
|
||||||
|
$context->popClassMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSerializationContext(Model $model): SerializationContext
|
||||||
|
{
|
||||||
|
if (isset($this->contexts[$model->getHash()])) {
|
||||||
|
$context = $this->contexts[$model->getHash()];
|
||||||
|
|
||||||
|
$stack = $context->getMetadataStack();
|
||||||
|
while (!$stack->isEmpty()) {
|
||||||
|
$stack->pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->metadataStacks[$model->getHash()] as $metadataCopy) {
|
||||||
|
$stack->unshift($metadataCopy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$context = SerializationContext::create();
|
||||||
|
|
||||||
|
if (null !== $model->getGroups()) {
|
||||||
|
$context->addExclusionStrategy(new GroupsExclusionStrategy($model->getGroups()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function computeGroups(Context $context, array $type = null)
|
||||||
|
{
|
||||||
|
if (null === $type || true !== $this->propertyTypeUsesGroups($type)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$groupsExclusion = $context->getExclusionStrategy();
|
||||||
|
if (!($groupsExclusion instanceof GroupsExclusionStrategy)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = $groupsExclusion->getGroupsFor($context);
|
||||||
|
if ([GroupsExclusionStrategy::DEFAULT_GROUP] === $groups) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,7 +178,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function describeItem(array $type, $property, array $groups = null, array $previousGroups = null)
|
private function describeItem(array $type, $property, Context $context)
|
||||||
{
|
{
|
||||||
$nestedTypeInfo = $this->getNestedTypeInArray($type);
|
$nestedTypeInfo = $this->getNestedTypeInArray($type);
|
||||||
if (null !== $nestedTypeInfo) {
|
if (null !== $nestedTypeInfo) {
|
||||||
@ -156,13 +193,13 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->describeItem($nestedType, $property->getAdditionalProperties(), $groups, $previousGroups);
|
$this->describeItem($nestedType, $property->getAdditionalProperties(), $context);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$property->setType('array');
|
$property->setType('array');
|
||||||
$this->describeItem($nestedType, $property->getItems(), $groups, $previousGroups);
|
$this->describeItem($nestedType, $property->getItems(), $context);
|
||||||
} elseif ('array' === $type['name']) {
|
} elseif ('array' === $type['name']) {
|
||||||
$property->setType('object');
|
$property->setType('object');
|
||||||
$property->merge(['additionalProperties' => []]);
|
$property->merge(['additionalProperties' => []]);
|
||||||
@ -177,12 +214,13 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
$property->setType('string');
|
$property->setType('string');
|
||||||
$property->setFormat('date-time');
|
$property->setFormat('date-time');
|
||||||
} else {
|
} else {
|
||||||
|
$groups = $this->computeGroups($context, $type);
|
||||||
|
|
||||||
$model = new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $type['name']), $groups);
|
$model = new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $type['name']), $groups);
|
||||||
$property->setRef($this->modelRegistry->register($model));
|
$property->setRef($this->modelRegistry->register($model));
|
||||||
|
|
||||||
if ($previousGroups) {
|
$this->contexts[$model->getHash()] = $context;
|
||||||
$this->previousGroups[$model->getHash()] = $previousGroups;
|
$this->metadataStacks[$model->getHash()] = clone $context->getMetadataStack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class JMSChatFriend
|
|||||||
/**
|
/**
|
||||||
* @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatLivingRoom")
|
* @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatLivingRoom")
|
||||||
* @Serializer\Expose
|
* @Serializer\Expose
|
||||||
* @Serializer\Groups({"Default"})
|
* @Serializer\Groups({"Default", "mini"})
|
||||||
*/
|
*/
|
||||||
private $living;
|
private $living;
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class JMSChatUser
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture")
|
* @Serializer\Type("Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture")
|
||||||
* @Serializer\Groups({"Default", "mini"})
|
* @Serializer\Groups({"mini"})
|
||||||
* @Serializer\Expose
|
* @Serializer\Expose
|
||||||
*/
|
*/
|
||||||
private $picture;
|
private $picture;
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
namespace Nelmio\ApiDocBundle\Tests\Functional;
|
namespace Nelmio\ApiDocBundle\Tests\Functional;
|
||||||
|
|
||||||
use JMS\Serializer\Visitor\SerializationVisitorInterface;
|
|
||||||
|
|
||||||
class JMSFunctionalTest extends WebTestCase
|
class JMSFunctionalTest extends WebTestCase
|
||||||
{
|
{
|
||||||
public function testModelPictureDocumentation()
|
public function testModelPictureDocumentation()
|
||||||
@ -20,11 +18,20 @@ class JMSFunctionalTest extends WebTestCase
|
|||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'only_direct_picture_mini' => [
|
'id' => [
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
], $this->getModel('JMSPicture')->toArray());
|
], $this->getModel('JMSPicture')->toArray());
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'only_direct_picture_mini' => [
|
||||||
|
'type' => 'integer',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], $this->getModel('JMSPicture_mini')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testModeChatDocumentation()
|
public function testModeChatDocumentation()
|
||||||
@ -48,7 +55,7 @@ class JMSFunctionalTest extends WebTestCase
|
|||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'picture' => [
|
'picture' => [
|
||||||
'$ref' => '#/definitions/JMSPicture2',
|
'$ref' => '#/definitions/JMSPicture',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
], $this->getModel('JMSChatUser')->toArray());
|
], $this->getModel('JMSChatUser')->toArray());
|
||||||
@ -212,12 +219,8 @@ class JMSFunctionalTest extends WebTestCase
|
|||||||
], $this->getModel('JMSDualComplex')->toArray());
|
], $this->getModel('JMSDualComplex')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNestedGroupsV1()
|
public function testNestedGroups()
|
||||||
{
|
{
|
||||||
if (interface_exists(SerializationVisitorInterface::class)){
|
|
||||||
$this->markTestSkipped('This applies only for jms/serializer v1.x');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => [
|
'properties' => [
|
||||||
@ -230,48 +233,18 @@ class JMSFunctionalTest extends WebTestCase
|
|||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'id1' => ['type' => 'integer'],
|
'id1' => ['type' => 'integer'],
|
||||||
'id2' => ['type' => 'integer'],
|
|
||||||
'id3' => ['type' => 'integer'],
|
'id3' => ['type' => 'integer'],
|
||||||
],
|
],
|
||||||
], $this->getModel('JMSChatRoom')->toArray());
|
], $this->getModel('JMSChatRoom')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNestedGroupsV2()
|
|
||||||
{
|
|
||||||
if (!interface_exists(SerializationVisitorInterface::class)){
|
|
||||||
$this->markTestSkipped('This applies only for jms/serializer v2.x');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'type' => 'object',
|
|
||||||
'properties' => [
|
|
||||||
'living' => ['$ref' => '#/definitions/JMSChatLivingRoom'],
|
|
||||||
'dining' => ['$ref' => '#/definitions/JMSChatRoom'],
|
|
||||||
],
|
|
||||||
], $this->getModel('JMSChatFriend')->toArray());
|
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'type' => 'object',
|
|
||||||
'properties' => [
|
|
||||||
'id2' => ['type' => 'integer'],
|
|
||||||
],
|
|
||||||
], $this->getModel('JMSChatRoom')->toArray());
|
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'type' => 'object',
|
|
||||||
'properties' => [
|
|
||||||
'id' => ['type' => 'integer'],
|
|
||||||
],
|
|
||||||
], $this->getModel('JMSChatLivingRoom')->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testModelComplexDocumentation()
|
public function testModelComplexDocumentation()
|
||||||
{
|
{
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'id' => ['type' => 'integer'],
|
'id' => ['type' => 'integer'],
|
||||||
'user' => ['$ref' => '#/definitions/JMSUser2'],
|
'user' => ['$ref' => '#/definitions/JMSUser'],
|
||||||
'name' => ['type' => 'string'],
|
'name' => ['type' => 'string'],
|
||||||
'virtual' => ['$ref' => '#/definitions/JMSUser'],
|
'virtual' => ['$ref' => '#/definitions/JMSUser'],
|
||||||
],
|
],
|
||||||
@ -280,19 +253,6 @@ class JMSFunctionalTest extends WebTestCase
|
|||||||
'user',
|
'user',
|
||||||
],
|
],
|
||||||
], $this->getModel('JMSComplex')->toArray());
|
], $this->getModel('JMSComplex')->toArray());
|
||||||
|
|
||||||
$this->assertEquals([
|
|
||||||
'type' => 'object',
|
|
||||||
'properties' => [
|
|
||||||
'id' => [
|
|
||||||
'type' => 'integer',
|
|
||||||
'title' => 'userid',
|
|
||||||
'description' => 'User id',
|
|
||||||
'readOnly' => true,
|
|
||||||
'example' => '1',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
], $this->getModel('JMSUser2')->toArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testYamlConfig()
|
public function testYamlConfig()
|
||||||
|
@ -50,7 +50,11 @@ class SwaggerUiTest extends WebTestCase
|
|||||||
'responses' => ['200' => ['description' => 'Test']],
|
'responses' => ['200' => ['description' => 'Test']],
|
||||||
]],
|
]],
|
||||||
];
|
];
|
||||||
$expected['definitions'] = ['Dummy' => $expected['definitions']['Dummy'], 'Test' => ['type' => 'string']];
|
$expected['definitions'] = [
|
||||||
|
'Dummy' => $expected['definitions']['Dummy'],
|
||||||
|
'Test' => ['type' => 'string'],
|
||||||
|
'JMSPicture_mini' => ['type' => 'object'],
|
||||||
|
];
|
||||||
|
|
||||||
yield ['/docs/test', 'test', $expected];
|
yield ['/docs/test', 'test', $expected];
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ use Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle;
|
|||||||
use FOS\RestBundle\FOSRestBundle;
|
use FOS\RestBundle\FOSRestBundle;
|
||||||
use JMS\SerializerBundle\JMSSerializerBundle;
|
use JMS\SerializerBundle\JMSSerializerBundle;
|
||||||
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
|
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
|
||||||
|
use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture;
|
||||||
use Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\VirtualTypeClassDoesNotExistsHandlerDefinedDescriber;
|
use Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\VirtualTypeClassDoesNotExistsHandlerDefinedDescriber;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
|
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
|
||||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||||
@ -164,6 +165,15 @@ class TestKernel extends Kernel
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'models' => [
|
||||||
|
'names' => [
|
||||||
|
[
|
||||||
|
'alias' => 'JMSPicture_mini',
|
||||||
|
'type' => JMSPicture::class,
|
||||||
|
'groups' => ['mini'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$def = new Definition(VirtualTypeClassDoesNotExistsHandlerDefinedDescriber::class);
|
$def = new Definition(VirtualTypeClassDoesNotExistsHandlerDefinedDescriber::class);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user