From fd6aa643d41a8b9eae1e24ba5aea87bb449e3ff4 Mon Sep 17 00:00:00 2001 From: Riccardo Tempesta Date: Sun, 22 Apr 2018 04:46:16 +0200 Subject: [PATCH 1/6] Enable WebAPI interface to handle parameters through constructor DTO setter methods are no more required, parameters can be handled through constructor for immutable state approach A setter fallback is supported --- .../Webapi/ServiceInputProcessor.php | 45 ++++++++++++++++++- .../SimpleConstructor.php | 43 ++++++++++++++++++ .../ServiceInputProcessor/TestService.php | 23 +++++++--- .../Test/Unit/ServiceInputProcessorTest.php | 21 ++++++++- 4 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index f1df2b0ee53f0..0c32396897c12 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -65,6 +65,11 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface */ private $serviceTypeToEntityTypeMap; + /** + * @var \Magento\Framework\ObjectManager\ConfigInterface + */ + private $config; + /** * Initialize dependencies. * @@ -73,6 +78,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param AttributeValueFactory $attributeValueFactory * @param CustomAttributeTypeLocatorInterface $customAttributeTypeLocator * @param MethodsMap $methodsMap + * @param \Magento\Framework\ObjectManager\ConfigInterface $config * @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap */ public function __construct( @@ -81,6 +87,7 @@ public function __construct( AttributeValueFactory $attributeValueFactory, CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, + \Magento\Framework\ObjectManager\ConfigInterface $config, ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null ) { $this->typeProcessor = $typeProcessor; @@ -90,6 +97,7 @@ public function __construct( $this->methodsMap = $methodsMap; $this->serviceTypeToEntityTypeMap = $serviceTypeToEntityTypeMap ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class); + $this->config = $config; } /** @@ -153,6 +161,33 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray return $inputData; } + /** + * @param string $className + * @param array $data + * @return array + * @throws \ReflectionException + */ + private function getConstructorData(string $className, array $data): array + { + $preferenceClass = $this->config->getPreference($className); + $class = new ClassReflection($preferenceClass ?: $className); + + $constructor = $class->getConstructor(); + if ($constructor === null) { + return []; + } + + $res = []; + $parameters = $constructor->getParameters(); + foreach ($parameters as $parameter) { + if (isset($data[$parameter->getName()])) { + $res[$parameter->getName()] = $data[$parameter->getName()]; + } + } + + return $res; + } + /** * Creates a new instance of the given class and populates it with the array of data. The data can * be in different forms depending on the adapter being used, REST vs. SOAP. For REST, the data is @@ -173,9 +208,17 @@ protected function _createFromArray($className, $data) if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { $className = substr($className, 0, -strlen('Interface')); } - $object = $this->objectManager->create($className); + // Primary method: assign to constructor parameters + $constructorArgs = $this->getConstructorData($className, $data); + $object = $this->objectManager->create($className, $constructorArgs); + + // Secondary method: fallback to setter methods foreach ($data as $propertyName => $value) { + if (isset($constructorArgs[$propertyName])) { + continue; + } + // Converts snake_case to uppercase CamelCase to help form getter/setter method names // This use case is for REST only. SOAP request data is already camel cased $camelCaseProperty = SimpleDataObjectConverter::snakeCaseToUpperCamelCase($propertyName); diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php new file mode 100644 index 0000000000000..a62fb70c50116 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php @@ -0,0 +1,43 @@ +entityId = $entityId; + $this->name = $name; + } + + /** + * @return int|null + */ + public function getEntityId() + { + return $this->entityId; + } + + /** + * @return string|null + */ + public function getName() + { + return $this->name; + } +} diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php index a5f91c101aa56..34515e8460b57 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php @@ -5,11 +5,6 @@ */ namespace Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\DataArray; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray; - class TestService { const DEFAULT_VALUE = 42; @@ -25,6 +20,15 @@ public function simple($entityId, $name) return [$entityId, $name]; } + /** + * @param \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleConstructor $simpleConstructor + * @return \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleConstructor + */ + public function simpleConstructor(SimpleConstructor $simpleConstructor) + { + return $simpleConstructor; + } + /** * @param int $entityId * @return string[] @@ -34,6 +38,15 @@ public function simpleDefaultValue($entityId = self::DEFAULT_VALUE) return [$entityId]; } + /** + * @param int $entityId + * @return string[] + */ + public function constructorArguments($entityId = self::DEFAULT_VALUE) + { + return [$entityId]; + } + /** * @param \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested $nested * @return \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php index a4c3f00e374a8..3393b325a8f16 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php @@ -9,6 +9,7 @@ use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\ServiceTypeToEntityTypeMap; +use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleConstructor; use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\WebapiBuilderFactory; use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray; use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\DataArray; @@ -59,8 +60,8 @@ protected function setUp() $this->objectManagerMock->expects($this->any()) ->method('create') ->willReturnCallback( - function ($className) use ($objectManager) { - return $objectManager->getObject($className); + function ($className, $arguments = []) use ($objectManager) { + return $objectManager->getObject($className, $arguments); } ); @@ -202,6 +203,22 @@ public function testNestedDataProperties() $this->assertEquals('Test', $details->getName()); } + public function testSimpleConstructorProperties() + { + $data = ['simpleConstructor' => ['entityId' => 15, 'name' => 'Test']]; + $result = $this->serviceInputProcessor->process( + \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService::class, + 'simpleConstructor', + $data + ); + $this->assertNotNull($result); + $arg = $result[0]; + + $this->assertTrue($arg instanceof SimpleConstructor); + $this->assertEquals(15, $arg->getEntityId()); + $this->assertEquals('Test', $arg->getName()); + } + public function testSimpleArrayProperties() { $data = ['ids' => [1, 2, 3, 4]]; From 04a53a8ac6b051172659c0b493eba6f508b860af Mon Sep 17 00:00:00 2001 From: Riccardo Tempesta Date: Sun, 22 Apr 2018 12:49:46 -0700 Subject: [PATCH 2/6] FIX for static tests failure _createFromArray will not be fixed due to its eventual deprecation, since a new immutable approach is provided --- .../Magento/Framework/Webapi/ServiceInputProcessor.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 0c32396897c12..f75044ec88c71 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -5,6 +5,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Webapi; use Magento\Framework\Webapi\ServiceTypeToEntityTypeMap; @@ -197,6 +199,7 @@ private function getConstructorData(string $className, array $data): array * @param array $data * @return object the newly created and populated object * @throws \Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _createFromArray($className, $data) { From 68e5dcd8c3bf9ad3b897d264579434de62940b62 Mon Sep 17 00:00:00 2001 From: Riccardo Tempesta Date: Sun, 22 Apr 2018 13:13:05 -0700 Subject: [PATCH 3/6] FIX static tests, missing declare(strict_types=1) --- .../Test/Unit/ServiceInputProcessor/SimpleConstructor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php index a62fb70c50116..f457a96e22a37 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor; class SimpleConstructor From 089e77a1efdbffd29c24fe7746e9e52c845ae56a Mon Sep 17 00:00:00 2001 From: Riccardo Tempesta Date: Sun, 13 May 2018 19:39:58 +0200 Subject: [PATCH 4/6] FIX optional constructor parameter for backward compatiblity --- .../Framework/Webapi/ServiceInputProcessor.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index ed803260c77ef..7f40937e98ac9 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -9,12 +9,12 @@ namespace Magento\Framework\Webapi; -use Magento\Framework\Webapi\ServiceTypeToEntityTypeMap; use Magento\Framework\Api\AttributeValue; use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\SerializationException; +use Magento\Framework\ObjectManager\ConfigInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Phrase; use Magento\Framework\Reflection\MethodsMap; @@ -68,7 +68,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface private $serviceTypeToEntityTypeMap; /** - * @var \Magento\Framework\ObjectManager\ConfigInterface + * @var ConfigInterface */ private $config; @@ -80,7 +80,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param AttributeValueFactory $attributeValueFactory * @param CustomAttributeTypeLocatorInterface $customAttributeTypeLocator * @param MethodsMap $methodsMap - * @param \Magento\Framework\ObjectManager\ConfigInterface $config + * @param ConfigInterface $config * @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap */ public function __construct( @@ -89,7 +89,7 @@ public function __construct( AttributeValueFactory $attributeValueFactory, CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, - \Magento\Framework\ObjectManager\ConfigInterface $config, + ConfigInterface $config = null, ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null ) { $this->typeProcessor = $typeProcessor; @@ -99,7 +99,8 @@ public function __construct( $this->methodsMap = $methodsMap; $this->serviceTypeToEntityTypeMap = $serviceTypeToEntityTypeMap ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class); - $this->config = $config; + $this->config = $config + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ConfigInterface::class); } /** From ac921c7a803aa5ee6e439f1fd76022b9f0de4b3f Mon Sep 17 00:00:00 2001 From: Volodymyr Zaets Date: Wed, 16 May 2018 13:05:49 +0300 Subject: [PATCH 5/6] Update ServiceInputProcessor.php Enable WebAPI interface to handle parameters through constructor #14801 - New optional param should be last in the params list. --- .../Magento/Framework/Webapi/ServiceInputProcessor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 7f40937e98ac9..5293e7ee1306b 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -89,8 +89,8 @@ public function __construct( AttributeValueFactory $attributeValueFactory, CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, - ConfigInterface $config = null, - ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null + ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null, + ConfigInterface $config = null ) { $this->typeProcessor = $typeProcessor; $this->objectManager = $objectManager; From 4426cdb5bf0ef1a3460520f2e0e6e911906c6125 Mon Sep 17 00:00:00 2001 From: Volodymyr Zaets Date: Wed, 16 May 2018 13:07:04 +0300 Subject: [PATCH 6/6] Enable WebAPI interface --- lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 5293e7ee1306b..26102f008c7c3 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -80,8 +80,8 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param AttributeValueFactory $attributeValueFactory * @param CustomAttributeTypeLocatorInterface $customAttributeTypeLocator * @param MethodsMap $methodsMap - * @param ConfigInterface $config * @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap + * @param ConfigInterface $config */ public function __construct( TypeProcessor $typeProcessor,