Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
ServiceInputProcessor.php
Go to the documentation of this file.
1 <?php
8 declare(strict_types=1);
9 
10 namespace Magento\Framework\Webapi;
11 
22 use Magento\Framework\Webapi\Exception as WebapiException;
23 use Zend\Code\Reflection\ClassReflection;
24 
33 {
34  const EXTENSION_ATTRIBUTES_TYPE = \Magento\Framework\Api\ExtensionAttributesInterface::class;
35 
39  protected $typeProcessor;
40 
44  protected $objectManager;
45 
50 
55 
59  protected $methodsMap;
60 
64  private $nameFinder;
65 
69  private $serviceTypeToEntityTypeMap;
70 
74  private $config;
75 
87  public function __construct(
93  ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null,
94  ConfigInterface $config = null
95  ) {
96  $this->typeProcessor = $typeProcessor;
97  $this->objectManager = $objectManager;
98  $this->attributeValueFactory = $attributeValueFactory;
99  $this->customAttributeTypeLocator = $customAttributeTypeLocator;
100  $this->methodsMap = $methodsMap;
101  $this->serviceTypeToEntityTypeMap = $serviceTypeToEntityTypeMap
102  ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class);
103  $this->config = $config
104  ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ConfigInterface::class);
105  }
106 
114  private function getNameFinder()
115  {
116  if ($this->nameFinder === null) {
118  ->get(\Magento\Framework\Reflection\NameFinder::class);
119  }
120  return $this->nameFinder;
121  }
122 
139  public function process($serviceClassName, $serviceMethodName, array $inputArray)
140  {
141  $inputData = [];
142  $inputError = [];
143  foreach ($this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName) as $param) {
144  $paramName = $param[MethodsMap::METHOD_META_NAME];
145  $snakeCaseParamName = strtolower(preg_replace("/(?<=\\w)(?=[A-Z])/", "_$1", $paramName));
146  if (isset($inputArray[$paramName]) || isset($inputArray[$snakeCaseParamName])) {
147  $paramValue = isset($inputArray[$paramName])
148  ? $inputArray[$paramName]
149  : $inputArray[$snakeCaseParamName];
150 
151  try {
152  $inputData[] = $this->convertValue($paramValue, $param[MethodsMap::METHOD_META_TYPE]);
153  } catch (SerializationException $e) {
154  throw new WebapiException(new Phrase($e->getMessage()));
155  }
156  } else {
158  $inputData[] = $param[MethodsMap::METHOD_META_DEFAULT_VALUE];
159  } else {
160  $inputError[] = $paramName;
161  }
162  }
163  }
164  $this->processInputError($inputError);
165  return $inputData;
166  }
167 
174  private function getConstructorData(string $className, array $data): array
175  {
176  $preferenceClass = $this->config->getPreference($className);
177  $class = new ClassReflection($preferenceClass ?: $className);
178 
179  $constructor = $class->getConstructor();
180  if ($constructor === null) {
181  return [];
182  }
183 
184  $res = [];
185  $parameters = $constructor->getParameters();
186  foreach ($parameters as $parameter) {
187  if (isset($data[$parameter->getName()])) {
188  $res[$parameter->getName()] = $data[$parameter->getName()];
189  }
190  }
191 
192  return $res;
193  }
194 
206  protected function _createFromArray($className, $data)
207  {
208  $data = is_array($data) ? $data : [];
209  // convert to string directly to avoid situations when $className is object
210  // which implements __toString method like \ReflectionObject
211  $className = (string) $className;
212  $class = new ClassReflection($className);
213  if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) {
214  $className = substr($className, 0, -strlen('Interface'));
215  }
216 
217  // Primary method: assign to constructor parameters
218  $constructorArgs = $this->getConstructorData($className, $data);
219  $object = $this->objectManager->create($className, $constructorArgs);
220 
221  // Secondary method: fallback to setter methods
222  foreach ($data as $propertyName => $value) {
223  if (isset($constructorArgs[$propertyName])) {
224  continue;
225  }
226 
227  // Converts snake_case to uppercase CamelCase to help form getter/setter method names
228  // This use case is for REST only. SOAP request data is already camel cased
229  $camelCaseProperty = SimpleDataObjectConverter::snakeCaseToUpperCamelCase($propertyName);
230  $methodName = $this->getNameFinder()->getGetterMethodName($class, $camelCaseProperty);
231  $methodReflection = $class->getMethod($methodName);
232  if ($methodReflection->isPublic()) {
233  $returnType = $this->typeProcessor->getGetterReturnType($methodReflection)['type'];
234  try {
235  $setterName = $this->getNameFinder()->getSetterMethodName($class, $camelCaseProperty);
236  } catch (\Exception $e) {
237  if (empty($value)) {
238  continue;
239  } else {
240  throw $e;
241  }
242  }
243  try {
244  if ($camelCaseProperty === 'CustomAttributes') {
245  $setterValue = $this->convertCustomAttributeValue($value, $className);
246  } else {
247  $setterValue = $this->convertValue($value, $returnType);
248  }
249  } catch (SerializationException $e) {
250  throw new SerializationException(
251  new Phrase(
252  'Error occurred during "%field_name" processing. %details',
253  ['field_name' => $propertyName, 'details' => $e->getMessage()]
254  )
255  );
256  }
257  $object->{$setterName}($setterValue);
258  }
259  }
260  return $object;
261  }
262 
271  protected function convertCustomAttributeValue($customAttributesValueArray, $dataObjectClassName)
272  {
273  $result = [];
274  $dataObjectClassName = ltrim($dataObjectClassName, '\\');
275 
276  foreach ($customAttributesValueArray as $key => $customAttribute) {
277  if (!is_array($customAttribute)) {
278  $customAttribute = [AttributeValue::ATTRIBUTE_CODE => $key, AttributeValue::VALUE => $customAttribute];
279  }
280 
281  list($customAttributeCode, $customAttributeValue) = $this->processCustomAttribute($customAttribute);
282 
283  $entityType = $this->serviceTypeToEntityTypeMap->getEntityType($dataObjectClassName);
284  if ($entityType) {
285  $type = $this->customAttributeTypeLocator->getType(
286  $customAttributeCode,
288  );
289  } else {
291  }
292 
293  if ($this->typeProcessor->isTypeAny($type) || $this->typeProcessor->isTypeSimple($type)
294  || !is_array($customAttributeValue)
295  ) {
296  try {
297  $attributeValue = $this->convertValue($customAttributeValue, $type);
298  } catch (SerializationException $e) {
299  throw new SerializationException(
300  new Phrase(
301  'Attribute "%attribute_code" has invalid value. %details',
302  ['attribute_code' => $customAttributeCode, 'details' => $e->getMessage()]
303  )
304  );
305  }
306  } else {
307  $attributeValue = $this->_createDataObjectForTypeAndArrayValue($type, $customAttributeValue);
308  }
309 
310  //Populate the attribute value data object once the value for custom attribute is derived based on type
311  $result[$customAttributeCode] = $this->attributeValueFactory->create()
312  ->setAttributeCode($customAttributeCode)
313  ->setValue($attributeValue);
314  }
315 
316  return $result;
317  }
318 
325  private function processCustomAttribute($customAttribute)
326  {
327  $camelCaseAttributeCodeKey = lcfirst(
329  );
330  // attribute code key could be snake or camel case, depending on whether SOAP or REST is used.
331  if (isset($customAttribute[AttributeValue::ATTRIBUTE_CODE])) {
332  $customAttributeCode = $customAttribute[AttributeValue::ATTRIBUTE_CODE];
333  } elseif (isset($customAttribute[$camelCaseAttributeCodeKey])) {
334  $customAttributeCode = $customAttribute[$camelCaseAttributeCodeKey];
335  } else {
336  $customAttributeCode = null;
337  }
338 
339  if (!$customAttributeCode && !isset($customAttribute[AttributeValue::VALUE])) {
340  throw new SerializationException(
341  new Phrase('An empty custom attribute is specified. Enter the attribute and try again.')
342  );
343  } elseif (!$customAttributeCode) {
344  throw new SerializationException(
345  new Phrase(
346  'A custom attribute is specified with a missing attribute code. Verify the code and try again.'
347  )
348  );
349  } elseif (!array_key_exists(AttributeValue::VALUE, $customAttribute)) {
350  throw new SerializationException(
351  new Phrase(
352  'The "' . $customAttributeCode .
353  '" attribute code doesn\'t have a value set. Enter the value and try again.'
354  )
355  );
356  }
357 
358  return [$customAttributeCode, $customAttribute[AttributeValue::VALUE]];
359  }
360 
368  protected function _createDataObjectForTypeAndArrayValue($type, $customAttributeValue)
369  {
370  if (substr($type, -2) === "[]") {
371  $type = substr($type, 0, -2);
372  $attributeValue = [];
373  foreach ($customAttributeValue as $value) {
374  $attributeValue[] = $this->_createFromArray($type, $value);
375  }
376  } else {
377  $attributeValue = $this->_createFromArray($type, $customAttributeValue);
378  }
379 
380  return $attributeValue;
381  }
382 
391  public function convertValue($data, $type)
392  {
393  $isArrayType = $this->typeProcessor->isArrayType($type);
394  if ($isArrayType && isset($data['item'])) {
395  $data = $this->_removeSoapItemNode($data);
396  }
397  if ($this->typeProcessor->isTypeSimple($type) || $this->typeProcessor->isTypeAny($type)) {
398  $result = $this->typeProcessor->processSimpleAndAnyType($data, $type);
399  } else {
401  if ($isArrayType) {
402  // Initializing the result for array type else it will return null for empty array
403  $result = is_array($data) ? [] : null;
404  $itemType = $this->typeProcessor->getArrayItemType($type);
405  if (is_array($data)) {
406  foreach ($data as $key => $item) {
407  $result[$key] = $this->_createFromArray($itemType, $item);
408  }
409  }
410  } else {
411  $result = $this->_createFromArray($type, $data);
412  }
413  }
414  return $result;
415  }
416 
424  protected function _removeSoapItemNode($value)
425  {
426  if (isset($value['item'])) {
427  if (is_array($value['item'])) {
428  $value = $value['item'];
429  } else {
430  return [$value['item']];
431  }
432  } else {
433  throw new \InvalidArgumentException('Value must be an array and must contain "item" field.');
434  }
440  $isAssociative = array_keys($value) !== range(0, count($value) - 1);
441  return $isAssociative ? [$value] : $value;
442  }
443 
451  protected function processInputError($inputError)
452  {
453  if (!empty($inputError)) {
454  $exception = new InputException();
455  foreach ($inputError as $errorParamField) {
456  $exception->addError(
457  new Phrase('"%fieldName" is required. Enter and try again.', ['fieldName' => $errorParamField])
458  );
459  }
460  if ($exception->wasErrorAdded()) {
461  throw $exception;
462  }
463  }
464  }
465 }
convertCustomAttributeValue($customAttributesValueArray, $dataObjectClassName)
is_subclass_of($obj, $className)
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
__construct(TypeProcessor $typeProcessor, ObjectManagerInterface $objectManager, AttributeValueFactory $attributeValueFactory, CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap=null, ConfigInterface $config=null)
$type
Definition: item.phtml:13
$_option $_optionId $class
Definition: date.phtml:13
$value
Definition: gender.phtml:16
_createDataObjectForTypeAndArrayValue($type, $customAttributeValue)
process($serviceClassName, $serviceMethodName, array $inputArray)
if($currentSelectedMethod==$_code) $className
Definition: form.phtml:31