From 7516ec60cbb091b7b234393d401dc819b2f27adf Mon Sep 17 00:00:00 2001 From: Akolzin Dmitry Date: Wed, 17 Feb 2021 09:31:36 +0300 Subject: [PATCH] JMS serializer supports (#3) * add callback argument value resolver * add jms serializer support * add phpdoc * fix FrontApiClientAuthenticator --- ArgumentResolver/CallbackValueResolver.php | 8 +-- ArgumentResolver/ClientValueResolver.php | 19 ++----- DependencyInjection/Configuration.php | 26 +++++++-- .../RetailCrmServiceExtension.php | 43 ++++++++++++-- Resources/doc/index.md | 29 ++++++++-- Serializer/Adapter.php | 29 ++++++++++ Serializer/JMSSerializerAdapter.php | 57 +++++++++++++++++++ Serializer/SymfonySerializerAdapter.php | 54 ++++++++++++++++++ .../CallbackValueResolverTest.php | 3 +- .../ClientValueResolverTest.php | 4 +- Tests/DataFixtures/RequestDto.php | 2 + .../DependencyInjection/ConfigurationTest.php | 24 +++++--- .../RetailCrmServiceExtensionTest.php | 11 +++- Tests/Serializer/JSMSerializerAdapterTest.php | 38 +++++++++++++ .../SymfonySerializerAdapterTest.php | 40 +++++++++++++ composer.json | 3 +- 16 files changed, 341 insertions(+), 49 deletions(-) create mode 100644 Serializer/Adapter.php create mode 100644 Serializer/JMSSerializerAdapter.php create mode 100644 Serializer/SymfonySerializerAdapter.php create mode 100644 Tests/Serializer/JSMSerializerAdapterTest.php create mode 100644 Tests/Serializer/SymfonySerializerAdapterTest.php diff --git a/ArgumentResolver/CallbackValueResolver.php b/ArgumentResolver/CallbackValueResolver.php index 063a4b5..3c412e5 100644 --- a/ArgumentResolver/CallbackValueResolver.php +++ b/ArgumentResolver/CallbackValueResolver.php @@ -2,10 +2,10 @@ namespace RetailCrm\ServiceBundle\ArgumentResolver; +use RetailCrm\ServiceBundle\Serializer\Adapter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; -use Symfony\Component\Serializer\SerializerInterface; use Generator; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -22,12 +22,12 @@ class CallbackValueResolver extends AbstractValueResolver implements ArgumentVal /** * CallbackValueResolver constructor. * - * @param SerializerInterface $serializer + * @param Adapter $serializer * @param ValidatorInterface $validator * @param array $requestSchema */ public function __construct( - SerializerInterface $serializer, + Adapter $serializer, ValidatorInterface $validator, array $requestSchema ) { @@ -55,7 +55,7 @@ class CallbackValueResolver extends AbstractValueResolver implements ArgumentVal public function resolve(Request $request, ArgumentMetadata $argument): Generator { $parameter = $this->search($request, $argument); - $data = $this->serializer->deserialize($request->request->get($parameter), $argument->getType(), 'json'); + $data = $this->serializer->deserialize($request->request->get($parameter), $argument->getType()); $this->validate($data); yield $data; diff --git a/ArgumentResolver/ClientValueResolver.php b/ArgumentResolver/ClientValueResolver.php index 0e0f940..7d9ed18 100644 --- a/ArgumentResolver/ClientValueResolver.php +++ b/ArgumentResolver/ClientValueResolver.php @@ -2,12 +2,10 @@ namespace RetailCrm\ServiceBundle\ArgumentResolver; +use RetailCrm\ServiceBundle\Serializer\Adapter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; -use Symfony\Component\Serializer\Exception\ExceptionInterface; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Generator; @@ -19,27 +17,24 @@ use Generator; class ClientValueResolver extends AbstractValueResolver implements ArgumentValueResolverInterface { private $serializer; - private $denormalizer; private $requestSchema; /** * ClientValueResolver constructor. * + * + * @param Adapter $serializer * @param ValidatorInterface $validator - * @param SerializerInterface $serializer - * @param DenormalizerInterface $denormalizer * @param array $requestSchema */ public function __construct( + Adapter $serializer, ValidatorInterface $validator, - SerializerInterface $serializer, - DenormalizerInterface $denormalizer, array $requestSchema ) { parent::__construct($validator); $this->serializer = $serializer; - $this->denormalizer = $denormalizer; $this->requestSchema = $requestSchema; } @@ -72,12 +67,10 @@ class ClientValueResolver extends AbstractValueResolver implements ArgumentValue * @param string $type * * @return object - * - * @throws ExceptionInterface */ private function handleGetData(array $data, string $type): object { - return $this->denormalizer->denormalize($data, $type); + return $this->serializer->arrayToObject($data, $type); } /** @@ -88,6 +81,6 @@ class ClientValueResolver extends AbstractValueResolver implements ArgumentValue */ private function handlePostData(string $data, string $type): object { - return $this->serializer->deserialize($data, $type, 'json'); + return $this->serializer->deserialize($data, $type); } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index b2f4693..557d96d 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -25,17 +25,31 @@ class Configuration implements ConfigurationInterface ->arrayNode('request_schema') ->children() ->arrayNode('callback') - ->arrayPrototype() - ->children() - ->scalarNode('type')->isRequired()->end() - ->arrayNode('params') - ->isRequired()->scalarPrototype()->end() + ->children() + ->arrayNode('supports') + ->arrayPrototype() + ->children() + ->scalarNode('type')->isRequired()->end() + ->arrayNode('params') + ->isRequired()->scalarPrototype()->end() + ->end() ->end() ->end() ->end() + ->scalarNode('serializer') + ->defaultValue('retail_crm_service.symfony_serializer.adapter') + ->end() ->end() + ->end() ->arrayNode('client') - ->scalarPrototype()->end() + ->children() + ->arrayNode('supports') + ->scalarPrototype()->end() + ->end() + ->scalarNode('serializer') + ->defaultValue('retail_crm_service.symfony_serializer.adapter') + ->end() + ->end() ->end() ->end() ->end() diff --git a/DependencyInjection/RetailCrmServiceExtension.php b/DependencyInjection/RetailCrmServiceExtension.php index 49c1b44..d32f08e 100644 --- a/DependencyInjection/RetailCrmServiceExtension.php +++ b/DependencyInjection/RetailCrmServiceExtension.php @@ -7,8 +7,11 @@ use RetailCrm\ServiceBundle\ArgumentResolver\ClientValueResolver; use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory; use RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator; use RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator; +use RetailCrm\ServiceBundle\Serializer\JMSSerializerAdapter; +use RetailCrm\ServiceBundle\Serializer\SymfonySerializerAdapter; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Reference; /** * Class RetailCrmServiceExtension @@ -27,24 +30,52 @@ class RetailCrmServiceExtension extends Extension $config = $this->processConfiguration($configuration, $configs); $container->setParameter( - 'retail_crm_service.request_schema.callback', - $config['request_schema']['callback'] + 'retail_crm_service.request_schema.callback.supports', + $config['request_schema']['callback']['supports'] ); $container->setParameter( - 'retail_crm_service.request_schema.client', - $config['request_schema']['client'] + 'retail_crm_service.request_schema.client.supports', + $config['request_schema']['client']['supports'] + ); + + $container->setParameter( + 'retail_crm_service.request_schema.callback.serializer', + $config['request_schema']['callback']['serializer'] + ); + + $container->setParameter( + 'retail_crm_service.request_schema.client.serializer', + $config['request_schema']['client']['serializer'] ); + $container + ->register(SymfonySerializerAdapter::class) + ->setAutowired(true); + $container->setAlias('retail_crm_service.symfony_serializer.adapter', SymfonySerializerAdapter::class); + + $container + ->register(JMSSerializerAdapter::class) + ->setAutowired(true); + $container->setAlias('retail_crm_service.jms_serializer.adapter', JMSSerializerAdapter::class); + $container ->register(CallbackValueResolver::class) - ->setArgument('$requestSchema', '%retail_crm_service.request_schema.callback%') + ->setArguments([ + new Reference($container->getParameter('retail_crm_service.request_schema.callback.serializer')), + new Reference('validator'), + $container->getParameter('retail_crm_service.request_schema.callback.supports') + ]) ->addTag('controller.argument_value_resolver', ['priority' => 50]) ->setAutowired(true); $container ->register(ClientValueResolver::class) - ->setArgument('$requestSchema', '%retail_crm_service.request_schema.client%') + ->setArguments([ + new Reference($container->getParameter('retail_crm_service.request_schema.client.serializer')), + new Reference('validator'), + $container->getParameter('retail_crm_service.request_schema.client.supports') + ]) ->addTag('controller.argument_value_resolver', ['priority' => 50]) ->setAutowired(true); diff --git a/Resources/doc/index.md b/Resources/doc/index.md index 3a1b787..443faab 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -20,7 +20,9 @@ Create bundle config file in `config/packages/retail_crm_service.yaml`: ```yaml retail_crm_service: - request_schema: ~ + request_schema: + callback: ~ + client: ~ ``` ### Deserializing incoming requests @@ -47,8 +49,8 @@ add to the config: retail_crm_service: request_schema: callback: - - type: App\Dto\Callback\Activity - params: ["activity"] + supports: + - { type: App\Dto\Callback\Activity, params: ["activity"] } ``` request automatically will be deserialization to $activity. @@ -73,7 +75,26 @@ add to the config: retail_crm_service: request_schema: client: - - App\Dto\Body + supports: + - App\Dto\Body +``` + +#### Serializers +At this time supported [Symfony serializer](https://symfony.com/doc/current/components/serializer.html) and [JMS serializer](https://jmsyst.com/libs/serializer). +By default, the library using a Symfony serializer. For use JMS install JMS serializer bundle - `composer require jms/serializer-bundle` +You can explicitly specify the type of serializer used for request schema: + +```yaml +retail_crm_service: + request_schema: + client: + supports: + # types + serializer: retail_crm_service.symfony_serializer.adapter # or retail_crm_service.jms_serializer.adapter + callback: + supports: + # types + serializer: retail_crm_service.jms_serializer.adapter # or retail_crm_service.symfony_serializer.adapter ``` ### Authentication diff --git a/Serializer/Adapter.php b/Serializer/Adapter.php new file mode 100644 index 0000000..5c797d4 --- /dev/null +++ b/Serializer/Adapter.php @@ -0,0 +1,29 @@ +serializer = $serializer; + $this->transformer = $transformer; + } + + /** + * {@inheritdoc } + */ + public function deserialize(string $data, string $type, string $format = 'json'): object + { + return $this->serializer->deserialize($data, $type, $format, $this->context); + } + + /** + * {@inheritdoc } + */ + public function arrayToObject(array $data, string $type, ?string $format = null): object + { + return $this->transformer->fromArray($data, $type, $this->context); + } + + /** + * @param Context $context + */ + public function setContext(Context $context): void + { + $this->context = $context; + } +} diff --git a/Serializer/SymfonySerializerAdapter.php b/Serializer/SymfonySerializerAdapter.php new file mode 100644 index 0000000..fdb4a85 --- /dev/null +++ b/Serializer/SymfonySerializerAdapter.php @@ -0,0 +1,54 @@ +serializer = $serializer; + $this->denormalizer = $denormalizer; + } + + /** + * {@inheritdoc } + */ + public function deserialize(string $data, string $type,string $format = 'json'): object + { + return $this->serializer->deserialize($data, $type, $format, $this->context); + } + + /** + * {@inheritdoc } + */ + public function arrayToObject(array $data, string $type, string $format = null): object + { + return $this->denormalizer->denormalize($data, $type, $format, $this->context); + } + + /** + * @param array $context + */ + public function setContext(array $context): void + { + $this->context = $context; + } +} diff --git a/Tests/ArgumentResolver/CallbackValueResolverTest.php b/Tests/ArgumentResolver/CallbackValueResolverTest.php index 660653d..c2d0c52 100644 --- a/Tests/ArgumentResolver/CallbackValueResolverTest.php +++ b/Tests/ArgumentResolver/CallbackValueResolverTest.php @@ -5,6 +5,7 @@ namespace RetailCrm\ServiceBundle\Tests\ArgumentResolver; use PHPUnit\Framework\TestCase; use RetailCrm\ServiceBundle\ArgumentResolver\CallbackValueResolver; use RetailCrm\ServiceBundle\Exceptions\InvalidRequestArgumentException; +use RetailCrm\ServiceBundle\Serializer\SymfonySerializerAdapter; use RetailCrm\ServiceBundle\Tests\DataFixtures\RequestDto; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -27,7 +28,7 @@ class CallbackValueResolverTest extends TestCase { $serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]); $this->resolver = new CallbackValueResolver( - $serializer, + new SymfonySerializerAdapter($serializer, $serializer), Validation::createValidatorBuilder() ->enableAnnotationMapping() ->getValidator(), diff --git a/Tests/ArgumentResolver/ClientValueResolverTest.php b/Tests/ArgumentResolver/ClientValueResolverTest.php index 0b27a2f..d281863 100644 --- a/Tests/ArgumentResolver/ClientValueResolverTest.php +++ b/Tests/ArgumentResolver/ClientValueResolverTest.php @@ -5,6 +5,7 @@ namespace RetailCrm\ServiceBundle\Tests\ArgumentResolver; use PHPUnit\Framework\TestCase; use RetailCrm\ServiceBundle\ArgumentResolver\ClientValueResolver; use RetailCrm\ServiceBundle\Exceptions\InvalidRequestArgumentException; +use RetailCrm\ServiceBundle\Serializer\SymfonySerializerAdapter; use RetailCrm\ServiceBundle\Tests\DataFixtures\RequestDto; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -27,11 +28,10 @@ class ClientValueResolverTest extends TestCase { $serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]); $this->resolver = new ClientValueResolver( + new SymfonySerializerAdapter($serializer, $serializer), Validation::createValidatorBuilder() ->enableAnnotationMapping() ->getValidator(), - $serializer, - $serializer, [ RequestDto::class ] diff --git a/Tests/DataFixtures/RequestDto.php b/Tests/DataFixtures/RequestDto.php index f6a3d84..84b16ba 100644 --- a/Tests/DataFixtures/RequestDto.php +++ b/Tests/DataFixtures/RequestDto.php @@ -3,6 +3,7 @@ namespace RetailCrm\ServiceBundle\Tests\DataFixtures; use Symfony\Component\Validator\Constraints as Assert; +use JMS\Serializer\Annotation as JMS; /** * Class RequestDto @@ -14,6 +15,7 @@ class RequestDto /** * @var string * @Assert\NotNull() + * @JMS\Type("string") */ public $param; } diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index ba97ded..5af77cf 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -21,14 +21,18 @@ class ConfigurationTest extends TestCase [ 'request_schema' => [ 'callback' => [ - [ - 'type' => 'type', - 'params' => ['param'] + 'supports' => [ + [ + 'type' => 'type', + 'params' => ['param'] + ] ] ], 'client' => [ - 'type1', - 'type2' + 'supports' => [ + 'type1', + 'type2' + ] ] ] ] @@ -44,14 +48,14 @@ class ConfigurationTest extends TestCase 'type' => 'type', 'params' => ['param'] ], - $config['request_schema']['callback'][0] + $config['request_schema']['callback']['supports'][0] ); static::assertEquals( [ 'type1', 'type2' ], - $config['request_schema']['client'] + $config['request_schema']['client']['supports'] ); } @@ -63,7 +67,9 @@ class ConfigurationTest extends TestCase [ 'request_schema' => [ 'client' => [ - 'type', + 'supports' => [ + 'type', + ] ] ] ] @@ -72,6 +78,6 @@ class ConfigurationTest extends TestCase $config = $processor->processConfiguration(new Configuration(), $configs); static::assertArrayHasKey('client', $config['request_schema']); - static::assertEquals(['type'], $config['request_schema']['client']); + static::assertEquals(['type'], $config['request_schema']['client']['supports']); } } diff --git a/Tests/DependencyInjection/RetailCrmServiceExtensionTest.php b/Tests/DependencyInjection/RetailCrmServiceExtensionTest.php index 471f198..ba0dcd0 100644 --- a/Tests/DependencyInjection/RetailCrmServiceExtensionTest.php +++ b/Tests/DependencyInjection/RetailCrmServiceExtensionTest.php @@ -31,7 +31,10 @@ class RetailCrmServiceExtensionTest extends TestCase $extension->load( [ [ - 'request_schema' => [] + 'request_schema' => [ + 'callback' => [], + 'client' => [] + ] ] ], $container @@ -44,8 +47,10 @@ class RetailCrmServiceExtensionTest extends TestCase public function testLoad(): void { - static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.callback')); - static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.client')); + static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.callback.supports')); + static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.callback.serializer')); + static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.client.supports')); + static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.client.serializer')); static::assertTrue($this->container->hasDefinition(CallbackValueResolver::class)); static::assertTrue($this->container->hasDefinition(ClientValueResolver::class)); static::assertTrue($this->container->hasDefinition(ErrorJsonResponseFactory::class)); diff --git a/Tests/Serializer/JSMSerializerAdapterTest.php b/Tests/Serializer/JSMSerializerAdapterTest.php new file mode 100644 index 0000000..1c7a1a4 --- /dev/null +++ b/Tests/Serializer/JSMSerializerAdapterTest.php @@ -0,0 +1,38 @@ +serializer = SerializerBuilder::create()->build(); + $this->transformer = SerializerBuilder::create()->build(); + } + + public function testDeserialize(): void + { + $adapter = new JMSSerializerAdapter($this->serializer, $this->transformer); + $object = $adapter->deserialize('{"param": "string"}', RequestDto::class,'json'); + + static::assertInstanceOf(RequestDto::class, $object); + static::assertEquals('string', $object->param); + } + + public function testArrayToObject(): void + { + $adapter = new JMSSerializerAdapter($this->serializer, $this->transformer); + $object = $adapter->arrayToObject(['param' => 'string'], RequestDto::class); + + static::assertInstanceOf(RequestDto::class, $object); + static::assertEquals('string', $object->param); + } +} diff --git a/Tests/Serializer/SymfonySerializerAdapterTest.php b/Tests/Serializer/SymfonySerializerAdapterTest.php new file mode 100644 index 0000000..1ce88f9 --- /dev/null +++ b/Tests/Serializer/SymfonySerializerAdapterTest.php @@ -0,0 +1,40 @@ +serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]); + $this->denormalizer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]); + } + + public function testDeserialize(): void + { + $adapter = new SymfonySerializerAdapter($this->serializer, $this->denormalizer); + $object = $adapter->deserialize('{"param": "string"}', RequestDto::class,'json'); + + static::assertInstanceOf(RequestDto::class, $object); + static::assertEquals('string', $object->param); + } + + public function testArrayToObject(): void + { + $adapter = new SymfonySerializerAdapter($this->serializer, $this->denormalizer); + $object = $adapter->arrayToObject(['param' => 'string'], RequestDto::class); + + static::assertInstanceOf(RequestDto::class, $object); + static::assertEquals('string', $object->param); + } +} diff --git a/composer.json b/composer.json index f4500bc..cdfb4a4 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,8 @@ "ext-json": "*", "phpunit/phpunit": "^8.0 || ^9.0", "doctrine/annotations": "^1.11", - "doctrine/cache": "^1.10" + "doctrine/cache": "^1.10", + "jms/serializer-bundle": "^3.8" }, "scripts": { "tests": "./vendor/bin/phpunit -c phpunit.xml.dist"