| Current Path : /var/www/clients/client3/web2/web/vendor/magento/module-webapi/Controller/Rest/ |
| Current File : /var/www/clients/client3/web2/web/vendor/magento/module-webapi/Controller/Rest/ParamsOverrider.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Webapi\Controller\Rest;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Webapi\Rest\Request\ParamOverriderInterface;
use Magento\Webapi\Model\Config\Converter;
use Magento\Framework\Reflection\MethodsMap;
use Magento\Framework\Api\SimpleDataObjectConverter;
/**
* Override parameter values
*/
class ParamsOverrider
{
/**
* @var ParamOverriderInterface[]
*/
private $paramOverriders;
/**
* @var MethodsMap
*/
private $methodsMap;
/**
* @var SimpleDataObjectConverter
*/
private $dataObjectConverter;
/**
* Initialize dependencies
*
* @param ParamOverriderInterface[] $paramOverriders
* @param SimpleDataObjectConverter|null $dataObjectConverter
*/
public function __construct(
array $paramOverriders = [],
SimpleDataObjectConverter $dataObjectConverter = null
) {
$this->paramOverriders = $paramOverriders;
$this->dataObjectConverter = $dataObjectConverter
?? ObjectManager::getInstance()->get(SimpleDataObjectConverter::class);
}
/**
* Override parameter values based on webapi.xml
*
* @param array $inputData Incoming data from request
* @param array $parameters Contains parameters to replace or default
* @return array Data in same format as $inputData with appropriate parameters added or changed
*/
public function override(array $inputData, array $parameters)
{
foreach ($parameters as $name => $paramData) {
$arrayKeys = explode('.', $name);
if ($paramData[Converter::KEY_FORCE] || !$this->isNestedArrayValueSet($inputData, $arrayKeys)) {
$paramValue = $paramData[Converter::KEY_VALUE];
if (isset($this->paramOverriders[$paramValue])) {
$value = $this->paramOverriders[$paramValue]->getOverriddenValue();
} else {
$value = $paramData[Converter::KEY_VALUE];
}
$this->setNestedArrayValue($inputData, $arrayKeys, $value);
}
}
return $inputData;
}
/**
* Determine if a nested array value is set.
*
* @param array $nestedArray
* @param string[] $arrayKeys
* @return bool true if array value is set
*/
protected function isNestedArrayValueSet($nestedArray, $arrayKeys)
{
//Converting input data to camelCase in order to process both snake and camel style data equally.
$currentArray = $this->dataObjectConverter->convertKeysToCamelCase($nestedArray);
foreach ($arrayKeys as $key) {
$key = SimpleDataObjectConverter::snakeCaseToCamelCase($key);
if (!isset($currentArray[$key])) {
return false;
}
$currentArray = &$currentArray[$key];
}
return true;
}
/**
* Set a nested array value.
*
* @param array &$nestedArray
* @param string[] $arrayKeys
* @param string $valueToSet
* @return void
*/
protected function setNestedArrayValue(&$nestedArray, $arrayKeys, $valueToSet)
{
$currentArray = &$nestedArray;
$lastKey = array_pop($arrayKeys);
foreach ($arrayKeys as $key) {
if (!array_key_exists($key, $currentArray)) {
//In case input data uses camelCase format
$key = SimpleDataObjectConverter::snakeCaseToCamelCase($key);
}
if (!isset($currentArray[$key])) {
$currentArray[$key] = [];
}
$currentArray = &$currentArray[$key];
}
//In case input data uses camelCase format
$camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase($lastKey);
if (array_key_exists($camelCaseKey, $currentArray)) {
$lastKey = $camelCaseKey;
}
$currentArray[$lastKey] = $valueToSet;
}
/**
* Override request body property value with matching url path parameter value
*
* This method assumes that webapi.xml url defines the substitution parameter as camelCase to the actual
* snake case key described as part of the api contract. example: /:parentId/nestedResource/:entityId.
* Here :entityId value will be used for overriding 'entity_id' property in the body.
* Since Webapi framework allows both camelCase and snakeCase, either of them will be substituted for now.
* If the request body is missing url path parameter as property, it will be added to the body.
* This method works only requests with scalar properties at top level or properties of single object embedded
* in the request body.
* Only the last path parameter value will be substituted from the url in case of multiple parameters.
*
* @param array $urlPathParams url path parameters as array
* @param array $requestBodyParams body parameters as array
* @param string $serviceClassName name of the service class that we are trying to call
* @param string $serviceMethodName name of the method that we are trying to call
* @return array
*/
public function overrideRequestBodyIdWithPathParam(
array $urlPathParams,
array $requestBodyParams,
$serviceClassName,
$serviceMethodName
) {
if (empty($urlPathParams)) {
return $requestBodyParams;
}
$pathParamValue = end($urlPathParams);
// Self apis should not be overridden
if ($pathParamValue === 'me') {
return $requestBodyParams;
}
$pathParamKey = key($urlPathParams);
// Check if the request data is a top level object of body
if (count($requestBodyParams) == 1 && is_array(end($requestBodyParams))) {
$requestDataKey = key($requestBodyParams);
if ($this->isPropertyDeclaredInDataObject(
$serviceClassName,
$serviceMethodName,
$requestDataKey,
$pathParamKey
)
) {
$this->substituteParameters($requestBodyParams[$requestDataKey], $pathParamKey, $pathParamValue);
} else {
$this->substituteParameters($requestBodyParams, $pathParamKey, $pathParamValue);
}
} else { // Else parameters passed as scalar values in body will be overridden
$this->substituteParameters($requestBodyParams, $pathParamKey, $pathParamValue);
}
return $requestBodyParams;
}
/**
* Check presence for both camelCase and snake_case keys in array and substitute if either is present
*
* @param array $requestData
* @param string $key
* @param string $value
* @return void
*/
private function substituteParameters(array &$requestData, $key, $value)
{
$snakeCaseKey = SimpleDataObjectConverter::camelCaseToSnakeCase($key);
$camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase($key);
if (isset($requestData[$camelCaseKey])) {
$requestData[$camelCaseKey] = $value;
} else {
$requestData[$snakeCaseKey] = $value;
}
}
/**
* Verify property in parameter's object
*
* @param string $serviceClassName name of the service class that we are trying to call
* @param string $serviceMethodName name of the method that we are trying to call
* @param string $serviceMethodParamName
* @param string $objectProperty
* @return bool
*/
private function isPropertyDeclaredInDataObject(
$serviceClassName,
$serviceMethodName,
$serviceMethodParamName,
$objectProperty
) {
if ($serviceClassName && $serviceMethodName) {
$methodParams = $this->getMethodsMap()->getMethodParams($serviceClassName, $serviceMethodName);
$index = array_search($serviceMethodParamName, array_column($methodParams, 'name'));
if ($index !== false) {
$paramObjectType = $methodParams[$index][MethodsMap::METHOD_META_TYPE];
$setter = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($objectProperty);
if (array_key_exists(
$setter,
$this->getMethodsMap()->getMethodsMap($paramObjectType)
)) {
return true;
}
}
}
return false;
}
/**
* The getter function to get MethodsMap object
*
* @return \Magento\Framework\Reflection\MethodsMap
*
* @deprecated 100.1.0
*/
private function getMethodsMap()
{
if ($this->methodsMap === null) {
$this->methodsMap = \Magento\Framework\App\ObjectManager::getInstance()
->get(MethodsMap::class);
}
return $this->methodsMap;
}
}