ParamsOverrider.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Webapi\Controller\Rest;
  7. use Magento\Framework\Webapi\Rest\Request\ParamOverriderInterface;
  8. use Magento\Webapi\Model\Config\Converter;
  9. use Magento\Framework\Reflection\MethodsMap;
  10. use Magento\Framework\Api\SimpleDataObjectConverter;
  11. /**
  12. * Override parameter values
  13. */
  14. class ParamsOverrider
  15. {
  16. /**
  17. * @var ParamOverriderInterface[]
  18. */
  19. private $paramOverriders;
  20. /**
  21. * @var MethodsMap
  22. */
  23. private $methodsMap;
  24. /**
  25. * Initialize dependencies
  26. *
  27. * @param ParamOverriderInterface[] $paramOverriders
  28. */
  29. public function __construct(
  30. array $paramOverriders = []
  31. ) {
  32. $this->paramOverriders = $paramOverriders;
  33. }
  34. /**
  35. * Override parameter values based on webapi.xml
  36. *
  37. * @param array $inputData Incoming data from request
  38. * @param array $parameters Contains parameters to replace or default
  39. * @return array Data in same format as $inputData with appropriate parameters added or changed
  40. */
  41. public function override(array $inputData, array $parameters)
  42. {
  43. foreach ($parameters as $name => $paramData) {
  44. $arrayKeys = explode('.', $name);
  45. if ($paramData[Converter::KEY_FORCE] || !$this->isNestedArrayValueSet($inputData, $arrayKeys)) {
  46. $paramValue = $paramData[Converter::KEY_VALUE];
  47. if (isset($this->paramOverriders[$paramValue])) {
  48. $value = $this->paramOverriders[$paramValue]->getOverriddenValue();
  49. } else {
  50. $value = $paramData[Converter::KEY_VALUE];
  51. }
  52. $this->setNestedArrayValue($inputData, $arrayKeys, $value);
  53. }
  54. }
  55. return $inputData;
  56. }
  57. /**
  58. * Determine if a nested array value is set.
  59. *
  60. * @param array &$nestedArray
  61. * @param string[] $arrayKeys
  62. * @return bool true if array value is set
  63. */
  64. protected function isNestedArrayValueSet(&$nestedArray, $arrayKeys)
  65. {
  66. $currentArray = &$nestedArray;
  67. foreach ($arrayKeys as $key) {
  68. if (!isset($currentArray[$key])) {
  69. return false;
  70. }
  71. $currentArray = &$currentArray[$key];
  72. }
  73. return true;
  74. }
  75. /**
  76. * Set a nested array value.
  77. *
  78. * @param array &$nestedArray
  79. * @param string[] $arrayKeys
  80. * @param string $valueToSet
  81. * @return void
  82. */
  83. protected function setNestedArrayValue(&$nestedArray, $arrayKeys, $valueToSet)
  84. {
  85. $currentArray = &$nestedArray;
  86. $lastKey = array_pop($arrayKeys);
  87. foreach ($arrayKeys as $key) {
  88. if (!isset($currentArray[$key])) {
  89. $currentArray[$key] = [];
  90. }
  91. $currentArray = &$currentArray[$key];
  92. }
  93. $currentArray[$lastKey] = $valueToSet;
  94. }
  95. /**
  96. * Override request body property value with matching url path parameter value
  97. *
  98. * This method assumes that webapi.xml url defines the substitution parameter as camelCase to the actual
  99. * snake case key described as part of the api contract. example: /:parentId/nestedResource/:entityId.
  100. * Here :entityId value will be used for overriding 'entity_id' property in the body.
  101. * Since Webapi framework allows both camelCase and snakeCase, either of them will be substituted for now.
  102. * If the request body is missing url path parameter as property, it will be added to the body.
  103. * This method works only requests with scalar properties at top level or properties of single object embedded
  104. * in the request body.
  105. * Only the last path parameter value will be substituted from the url in case of multiple parameters.
  106. *
  107. * @param array $urlPathParams url path parameters as array
  108. * @param array $requestBodyParams body parameters as array
  109. * @param string $serviceClassName name of the service class that we are trying to call
  110. * @param string $serviceMethodName name of the method that we are trying to call
  111. * @return array
  112. */
  113. public function overrideRequestBodyIdWithPathParam(
  114. array $urlPathParams,
  115. array $requestBodyParams,
  116. $serviceClassName,
  117. $serviceMethodName
  118. ) {
  119. if (empty($urlPathParams)) {
  120. return $requestBodyParams;
  121. }
  122. $pathParamValue = end($urlPathParams);
  123. // Self apis should not be overridden
  124. if ($pathParamValue === 'me') {
  125. return $requestBodyParams;
  126. }
  127. $pathParamKey = key($urlPathParams);
  128. // Check if the request data is a top level object of body
  129. if (count($requestBodyParams) == 1 && is_array(end($requestBodyParams))) {
  130. $requestDataKey = key($requestBodyParams);
  131. if ($this->isPropertyDeclaredInDataObject(
  132. $serviceClassName,
  133. $serviceMethodName,
  134. $requestDataKey,
  135. $pathParamKey
  136. )
  137. ) {
  138. $this->substituteParameters($requestBodyParams[$requestDataKey], $pathParamKey, $pathParamValue);
  139. } else {
  140. $this->substituteParameters($requestBodyParams, $pathParamKey, $pathParamValue);
  141. }
  142. } else { // Else parameters passed as scalar values in body will be overridden
  143. $this->substituteParameters($requestBodyParams, $pathParamKey, $pathParamValue);
  144. }
  145. return $requestBodyParams;
  146. }
  147. /**
  148. * Check presence for both camelCase and snake_case keys in array and substitute if either is present
  149. *
  150. * @param array $requestData
  151. * @param string $key
  152. * @param string $value
  153. * @return void
  154. */
  155. private function substituteParameters(array &$requestData, $key, $value)
  156. {
  157. $snakeCaseKey = SimpleDataObjectConverter::camelCaseToSnakeCase($key);
  158. $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase($key);
  159. if (isset($requestData[$camelCaseKey])) {
  160. $requestData[$camelCaseKey] = $value;
  161. } else {
  162. $requestData[$snakeCaseKey] = $value;
  163. }
  164. }
  165. /**
  166. * Verify property in parameter's object
  167. *
  168. * @param string $serviceClassName name of the service class that we are trying to call
  169. * @param string $serviceMethodName name of the method that we are trying to call
  170. * @param string $serviceMethodParamName
  171. * @param string $objectProperty
  172. * @return bool
  173. */
  174. private function isPropertyDeclaredInDataObject(
  175. $serviceClassName,
  176. $serviceMethodName,
  177. $serviceMethodParamName,
  178. $objectProperty
  179. ) {
  180. if ($serviceClassName && $serviceMethodName) {
  181. $methodParams = $this->getMethodsMap()->getMethodParams($serviceClassName, $serviceMethodName);
  182. $index = array_search($serviceMethodParamName, array_column($methodParams, 'name'));
  183. if ($index !== false) {
  184. $paramObjectType = $methodParams[$index][MethodsMap::METHOD_META_TYPE];
  185. $setter = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($objectProperty);
  186. if (array_key_exists(
  187. $setter,
  188. $this->getMethodsMap()->getMethodsMap($paramObjectType)
  189. )) {
  190. return true;
  191. }
  192. }
  193. }
  194. return false;
  195. }
  196. /**
  197. * The getter function to get MethodsMap object
  198. *
  199. * @return \Magento\Framework\Reflection\MethodsMap
  200. *
  201. * @deprecated 100.1.0
  202. */
  203. private function getMethodsMap()
  204. {
  205. if ($this->methodsMap === null) {
  206. $this->methodsMap = \Magento\Framework\App\ObjectManager::getInstance()
  207. ->get(MethodsMap::class);
  208. }
  209. return $this->methodsMap;
  210. }
  211. }