Skip to content

Enable WebAPI interface to handle parameters through constructor #14801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

52 changes: 49 additions & 3 deletions lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,6 +67,11 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface
*/
private $serviceTypeToEntityTypeMap;

/**
* @var ConfigInterface
*/
private $config;

/**
* Initialize dependencies.
*
Expand All @@ -75,14 +81,16 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface
* @param CustomAttributeTypeLocatorInterface $customAttributeTypeLocator
* @param MethodsMap $methodsMap
* @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap
* @param ConfigInterface $config
*/
public function __construct(
TypeProcessor $typeProcessor,
ObjectManagerInterface $objectManager,
AttributeValueFactory $attributeValueFactory,
CustomAttributeTypeLocatorInterface $customAttributeTypeLocator,
MethodsMap $methodsMap,
ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null
ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null,
ConfigInterface $config = null
) {
$this->typeProcessor = $typeProcessor;
$this->objectManager = $objectManager;
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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
Expand All @@ -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)
{
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
/**
* 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
{
/**
* @var int
*/
private $entityId;

/**
* @var string
*/
private $name;

public function __construct(
int $entityId,
string $name
) {
$this->entityId = $entityId;
$this->name = $name;
}

/**
* @return int|null
*/
public function getEntityId()
{
return $this->entityId;
}

/**
* @return string|null
*/
public function getName()
{
return $this->name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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[]
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
);

Expand Down Expand Up @@ -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]];
Expand Down