diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index bcbecd37d90df..26102f008c7c3 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -5,15 +5,16 @@ * 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; 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; @@ -66,6 +67,11 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface */ private $serviceTypeToEntityTypeMap; + /** + * @var ConfigInterface + */ + private $config; + /** * Initialize dependencies. * @@ -75,6 +81,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param CustomAttributeTypeLocatorInterface $customAttributeTypeLocator * @param MethodsMap $methodsMap * @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap + * @param ConfigInterface $config */ public function __construct( TypeProcessor $typeProcessor, @@ -82,7 +89,8 @@ public function __construct( AttributeValueFactory $attributeValueFactory, CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, - ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null + ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null, + ConfigInterface $config = null ) { $this->typeProcessor = $typeProcessor; $this->objectManager = $objectManager; @@ -91,6 +99,8 @@ public function __construct( $this->methodsMap = $methodsMap; $this->serviceTypeToEntityTypeMap = $serviceTypeToEntityTypeMap ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class); + $this->config = $config + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ConfigInterface::class); } /** @@ -154,6 +164,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 @@ -163,6 +200,7 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray * @param array $data * @return object the newly created and populated object * @throws \Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _createFromArray($className, $data) { @@ -174,9 +212,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..f457a96e22a37 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php @@ -0,0 +1,45 @@ +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]];