From d00d145b2016493a07a638b9cf48ddb6a9a738bc Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Sun, 11 Nov 2018 16:11:27 +0100 Subject: [PATCH 1/6] allow jms serializer bundle 3.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7f2d50a..fa0d364 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "api-platform/core": "^2.1.0", "friendsofsymfony/rest-bundle": "^2.0", "willdurand/hateoas-bundle": "^1.0", - "jms/serializer-bundle": "^2.0" + "jms/serializer-bundle": "^2.0|^3.0" }, "suggest": { "api-platform/core": "For using an API oriented framework.", From 66bcf029035d6e868f475d34c6b16ee430df6cec Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Sun, 11 Nov 2018 16:12:15 +0100 Subject: [PATCH 2/6] when the naming strategy is not available, we are using jms/serializer 2.0 --- DependencyInjection/NelmioApiDocExtension.php | 6 +++++- ModelDescriber/JMSModelDescriber.php | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index 0bcb69d..7ddd0ce 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\DependencyInjection; use FOS\RestBundle\Controller\Annotations\ParamInterface; +use JMS\Serializer\Visitor\SerializationVisitorInterface; use Nelmio\ApiDocBundle\ApiDocGenerator; use Nelmio\ApiDocBundle\Describer\ExternalDocDescriber; use Nelmio\ApiDocBundle\Describer\RouteDescriber; @@ -22,6 +23,7 @@ use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -141,11 +143,13 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI // JMS metadata support if ($config['models']['use_jms']) { + $jmsNamingStrategy = interface_exists(SerializationVisitorInterface::class) ? null : new Reference('jms_serializer.naming_strategy'); + $container->register('nelmio_api_doc.model_describers.jms', JMSModelDescriber::class) ->setPublic(false) ->setArguments([ new Reference('jms_serializer.metadata_factory'), - new Reference('jms_serializer.naming_strategy'), + $jmsNamingStrategy, new Reference('annotation_reader'), ]) ->addTag('nelmio_api_doc.model_describer', ['priority' => 50]); diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index c9d4488..518023d 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -42,7 +42,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn public function __construct( MetadataFactoryInterface $factory, - PropertyNamingStrategyInterface $namingStrategy, + PropertyNamingStrategyInterface $namingStrategy = null, Reader $reader ) { $this->factory = $factory; @@ -81,8 +81,9 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn $previousGroups = $groups; $groups = $groups[$item->name]; } elseif (!isset($groups[$item->name]) && !empty($this->previousGroups[$model->getHash()])) { - // $groups = $this->previousGroups[spl_object_hash($model)]; use this for jms/serializer 2.0 - $groups = false === $this->propertyTypeUsesGroups($item->type) ? null : [GroupsExclusionStrategy::DEFAULT_GROUP]; + $groups = false === $this->propertyTypeUsesGroups($item->type) + ? null + : ($this->namingStrategy ? [GroupsExclusionStrategy::DEFAULT_GROUP] : $this->previousGroups[$model->getHash()]); } elseif (is_array($groups)) { $groups = array_filter($groups, 'is_scalar'); } @@ -91,7 +92,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn $groups = null; } - $name = $this->namingStrategy->translateName($item); + $name = $this->namingStrategy ? $this->namingStrategy->translateName($item) : $item->serializedName; // read property options from Swagger Property annotation if it exists if (null !== $item->reflection) { $property = $properties->get($annotationsReader->getPropertyName($item->reflection, $name)); From d15b4123c1529542c00ac3f21909bd6f14703485 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Fri, 11 Jan 2019 10:40:53 +0100 Subject: [PATCH 3/6] add hateoas v3 compat --- ModelDescriber/JMSModelDescriber.php | 17 +++++++++++++---- Tests/Functional/Entity/BazingaUser.php | 2 +- composer.json | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index 518023d..3a6449d 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -13,6 +13,7 @@ namespace Nelmio\ApiDocBundle\ModelDescriber; use Doctrine\Common\Annotations\Reader; use EXSyst\Component\Swagger\Schema; +use JMS\Serializer\Annotation\VirtualProperty; use JMS\Serializer\Exclusion\GroupsExclusionStrategy; use JMS\Serializer\Naming\PropertyNamingStrategyInterface; use JMS\Serializer\SerializationContext; @@ -94,10 +95,18 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn $name = $this->namingStrategy ? $this->namingStrategy->translateName($item) : $item->serializedName; // read property options from Swagger Property annotation if it exists - if (null !== $item->reflection) { - $property = $properties->get($annotationsReader->getPropertyName($item->reflection, $name)); - $annotationsReader->updateProperty($item->reflection, $property, $groups); - } else { + try { + if (property_exists($item, 'reflection') && null !== $item->reflection) { + $reflection = $item->reflection; + } elseif ($item instanceof VirtualProperty) { + $reflection = new \ReflectionProperty($item->class, $item->name); + } else { + $reflection = new \ReflectionProperty($item->class, $item->name); + } + + $property = $properties->get($annotationsReader->getPropertyName($reflection, $name)); + $annotationsReader->updateProperty($reflection, $property, $groups); + } catch (\ReflectionException $e) { $property = $properties->get($name); } diff --git a/Tests/Functional/Entity/BazingaUser.php b/Tests/Functional/Entity/BazingaUser.php index 128453a..0eb4a1c 100644 --- a/Tests/Functional/Entity/BazingaUser.php +++ b/Tests/Functional/Entity/BazingaUser.php @@ -18,7 +18,7 @@ use Hateoas\Configuration\Annotation as Hateoas; * * @Hateoas\Relation(name="example", attributes={"str_att":"bar", "float_att":5.6, "bool_att": false}, href="http://www.example.com") * @Hateoas\Relation(name="route", href=@Hateoas\Route("foo")) - * @Hateoas\Relation(name="route", attributes={"foo":"bar"}, embedded=@Hateoas\Embedded("expr(foo)")) + * @Hateoas\Relation(name="route", attributes={"foo":"bar"}, embedded=@Hateoas\Embedded("expr(service('xx'))")) */ class BazingaUser { diff --git a/composer.json b/composer.json index fa0d364..16d96af 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "api-platform/core": "^2.1.0", "friendsofsymfony/rest-bundle": "^2.0", - "willdurand/hateoas-bundle": "^1.0", + "willdurand/hateoas-bundle": "^1.0|^2.0", "jms/serializer-bundle": "^2.0|^3.0" }, "suggest": { From e738102514b736b09bc5958eccafa07e886330be Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Sat, 26 Jan 2019 14:48:06 +0100 Subject: [PATCH 4/6] test low deps on 7.2 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index bd845b9..cd4aa04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,8 @@ matrix: include: - php: 7.0 env: COMPOSER_FLAGS="--prefer-lowest" + - php: 7.2 + env: COMPOSER_FLAGS="--prefer-lowest" before_install: - phpenv config-rm xdebug.ini From 0fb5d7afa24f576c5f16a0a67f50578258a8402f Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Sat, 26 Jan 2019 14:52:13 +0100 Subject: [PATCH 5/6] jms 2.0 groups tested --- DependencyInjection/NelmioApiDocExtension.php | 1 - ModelDescriber/JMSModelDescriber.php | 12 ++-- Tests/Functional/Controller/JMSController.php | 13 +++++ .../Entity/NestedGroup/JMSChatFriend.php | 42 ++++++++++++++ .../Entity/NestedGroup/JMSChatLivingRoom.php | 28 ++++++++++ .../Entity/NestedGroup/JMSChatRoom.php | 42 ++++++++++++++ .../Entity/NestedGroup/JMSChatRoomUser.php | 35 ++++++++++++ Tests/Functional/JMSFunctionalTest.php | 55 +++++++++++++++++++ 8 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 Tests/Functional/Entity/NestedGroup/JMSChatFriend.php create mode 100644 Tests/Functional/Entity/NestedGroup/JMSChatLivingRoom.php create mode 100644 Tests/Functional/Entity/NestedGroup/JMSChatRoom.php create mode 100644 Tests/Functional/Entity/NestedGroup/JMSChatRoomUser.php diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index 7ddd0ce..e109a39 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -23,7 +23,6 @@ use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index 3a6449d..f092a85 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -68,6 +68,8 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn $annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry); $annotationsReader->updateDefinition(new \ReflectionClass($className), $schema); + $isJmsV1 = null !== $this->namingStrategy; + $properties = $schema->getProperties(); foreach ($metadata->propertyMetadata as $item) { // filter groups @@ -84,8 +86,10 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn } elseif (!isset($groups[$item->name]) && !empty($this->previousGroups[$model->getHash()])) { $groups = false === $this->propertyTypeUsesGroups($item->type) ? null - : ($this->namingStrategy ? [GroupsExclusionStrategy::DEFAULT_GROUP] : $this->previousGroups[$model->getHash()]); - } elseif (is_array($groups)) { + : ($isJmsV1 ? [GroupsExclusionStrategy::DEFAULT_GROUP] : $this->previousGroups[$model->getHash()]); + } + + if (is_array($groups)) { $groups = array_filter($groups, 'is_scalar'); } @@ -93,10 +97,10 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn $groups = null; } - $name = $this->namingStrategy ? $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 try { - if (property_exists($item, 'reflection') && null !== $item->reflection) { + if (true === $isJmsV1 && property_exists($item, 'reflection') && null !== $item->reflection) { $reflection = $item->reflection; } elseif ($item instanceof VirtualProperty) { $reflection = new \ReflectionProperty($item->class, $item->name); diff --git a/Tests/Functional/Controller/JMSController.php b/Tests/Functional/Controller/JMSController.php index c932c57..47742ef 100644 --- a/Tests/Functional/Controller/JMSController.php +++ b/Tests/Functional/Controller/JMSController.php @@ -17,6 +17,7 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSDualComplex; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSNamingStrategyConstraints; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSUser; use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChat; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatRoomUser; use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatUser; use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture; use Nelmio\ApiDocBundle\Tests\Functional\Entity\VirtualProperty; @@ -123,4 +124,16 @@ class JMSController public function minUserAction() { } + + /** + * @Route("/api/jms_mini_user_nested", methods={"GET"}) + * @SWG\Response( + * response=200, + * description="Success", + * @Model(type=JMSChatRoomUser::class, groups={"mini", "friend": {"living":{"Default"}}}) + * ) + */ + public function minUserNestedAction() + { + } } diff --git a/Tests/Functional/Entity/NestedGroup/JMSChatFriend.php b/Tests/Functional/Entity/NestedGroup/JMSChatFriend.php new file mode 100644 index 0000000..00792b8 --- /dev/null +++ b/Tests/Functional/Entity/NestedGroup/JMSChatFriend.php @@ -0,0 +1,42 @@ +getModel('JMSDualComplex')->toArray()); } + public function testNestedGroupsV1() + { + if (interface_exists(SerializationVisitorInterface::class)){ + $this->markTestSkipped('This applies only for jms/serializer v1.x'); + } + + $this->assertEquals([ + 'type' => 'object', + 'properties' => [ + 'living' => ['$ref' => '#/definitions/JMSChatLivingRoom'], + 'dining' => ['$ref' => '#/definitions/JMSChatRoom'], + ], + ], $this->getModel('JMSChatFriend')->toArray()); + + $this->assertEquals([ + 'type' => 'object', + 'properties' => [ + 'id1' => ['type' => 'integer'], + 'id2' => ['type' => 'integer'], + 'id3' => ['type' => 'integer'], + ], + ], $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() { $this->assertEquals([ From fd7b5e167915c87473dff5a8d148abb7b6f111f6 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Sat, 26 Jan 2019 21:03:07 +0100 Subject: [PATCH 6/6] fix missing previous recursive call --- ModelDescriber/JMSModelDescriber.php | 2 +- Tests/Functional/JMSFunctionalTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index f092a85..6c2dd77 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -165,7 +165,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn } $property->setType('array'); - $this->describeItem($nestedType, $property->getItems(), $groups); + $this->describeItem($nestedType, $property->getItems(), $groups, $previousGroups); } elseif ('array' === $type['name']) { $property->setType('object'); $property->merge(['additionalProperties' => []]); diff --git a/Tests/Functional/JMSFunctionalTest.php b/Tests/Functional/JMSFunctionalTest.php index 52544d9..b01783b 100644 --- a/Tests/Functional/JMSFunctionalTest.php +++ b/Tests/Functional/JMSFunctionalTest.php @@ -48,7 +48,7 @@ class JMSFunctionalTest extends WebTestCase 'type' => 'object', 'properties' => [ 'picture' => [ - '$ref' => '#/definitions/JMSPicture', + '$ref' => '#/definitions/JMSPicture2', ], ], ], $this->getModel('JMSChatUser')->toArray());