/** * Convert the input array from key-value format to a list of parameters suitable for the specified class / method. * * The input array should have the field name as the key, and the value will either be a primitive or another * key-value array. The top level of this array needs keys that match the names of the parameters on the * service method. * * Mismatched types are caught by the PHP runtime, not explicitly checked for by this code. * * @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 array $inputArray data to send to method in key-value format * @return array list of parameters that can be used to call the service method * @throws InputException if no value is provided for required parameters * @throws WebapiException */ public function process($serviceClassName, $serviceMethodName, array $inputArray) { $inputData = []; $inputError = []; foreach ($this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName) as $param) { $paramName = $param[MethodsMap::METHOD_META_NAME]; $snakeCaseParamName = strtolower(preg_replace("/(?<=\\w)(?=[A-Z])/", "_\$1", $paramName)); if (isset($inputArray[$paramName]) || isset($inputArray[$snakeCaseParamName])) { $paramValue = isset($inputArray[$paramName]) ? $inputArray[$paramName] : $inputArray[$snakeCaseParamName]; try { $inputData[] = $this->convertValue($paramValue, $param[MethodsMap::METHOD_META_TYPE]); } catch (SerializationException $e) { throw new WebapiException(new Phrase($e->getMessage())); } } else { if ($param[MethodsMap::METHOD_META_HAS_DEFAULT_VALUE]) { $inputData[] = $param[MethodsMap::METHOD_META_DEFAULT_VALUE]; } else { $inputError[] = $paramName; } } } $this->processInputError($inputError); return $inputData; }
/** * Validate service method * * @param string $serviceMethod * @param string $topicName * @param string $className * @param string $methodName * @return void */ public function validateServiceMethod($serviceMethod, $topicName, $className, $methodName) { try { $this->methodsMap->getMethodParams($className, $methodName); } catch (\Exception $e) { throw new \LogicException(sprintf('Service method specified in the definition of topic "%s" is not available. Given "%s"', $topicName, $serviceMethod)); } }
/** * Extract service method metadata. * * @param string $className * @param string $methodName * @return array */ public function extractMethodMetadata($className, $methodName) { $result = [Config::SCHEMA_METHOD_PARAMS => [], Config::SCHEMA_METHOD_RETURN_TYPE => $this->methodsMap->getMethodReturnType($className, $methodName), Config::SCHEMA_METHOD_HANDLER => [Config::HANDLER_TYPE => $className, Config::HANDLER_METHOD => $methodName]]; $paramsMeta = $this->methodsMap->getMethodParams($className, $methodName); foreach ($paramsMeta as $paramPosition => $paramMeta) { $result[Config::SCHEMA_METHOD_PARAMS][] = [Config::SCHEMA_METHOD_PARAM_NAME => $paramMeta[MethodsMap::METHOD_META_NAME], Config::SCHEMA_METHOD_PARAM_POSITION => $paramPosition, Config::SCHEMA_METHOD_PARAM_IS_REQUIRED => !$paramMeta[MethodsMap::METHOD_META_HAS_DEFAULT_VALUE], Config::SCHEMA_METHOD_PARAM_TYPE => $paramMeta[MethodsMap::METHOD_META_TYPE]]; } return $result; }
/** * @param ExtensibleDataInterface $dataObject * @param string $getterMethodName * @param string $methodName * @param array $value * @param string $interfaceName * @return $this */ protected function setComplexValue(ExtensibleDataInterface $dataObject, $getterMethodName, $methodName, array $value, $interfaceName) { if ($interfaceName == null) { $interfaceName = get_class($dataObject); } $returnType = $this->methodsMapProcessor->getMethodReturnType($interfaceName, $getterMethodName); if ($this->typeProcessor->isTypeSimple($returnType)) { $dataObject->{$methodName}($value); return $this; } if ($this->typeProcessor->isArrayType($returnType)) { $type = $this->typeProcessor->getArrayItemType($returnType); $objects = []; foreach ($value as $arrayElementData) { $object = $this->objectFactory->create($type, []); $this->populateWithArray($object, $arrayElementData, $type); $objects[] = $object; } $dataObject->{$methodName}($objects); return $this; } if (is_subclass_of($returnType, '\\Magento\\Framework\\Api\\ExtensibleDataInterface')) { $object = $this->objectFactory->create($returnType, []); $this->populateWithArray($object, $value, $returnType); } else { if (is_subclass_of($returnType, '\\Magento\\Framework\\Api\\ExtensionAttributesInterface')) { $object = $this->extensionFactory->create(get_class($dataObject), $value); } else { $object = $this->objectFactory->create($returnType, $value); } } $dataObject->{$methodName}($object); return $this; }
/** * Use class reflection on given data interface to build output data array * * @param mixed $dataObject * @param string $dataObjectType * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function buildOutputDataArray($dataObject, $dataObjectType) { $methods = $this->methodsMapProcessor->getMethodsMap($dataObjectType); $outputData = []; /** @var MethodReflection $method */ foreach (array_keys($methods) as $methodName) { if (!$this->methodsMapProcessor->isMethodValidForDataField($dataObjectType, $methodName)) { continue; } $value = $dataObject->{$methodName}(); $isMethodReturnValueRequired = $this->methodsMapProcessor->isMethodReturnValueRequired($dataObjectType, $methodName); if ($value === null && !$isMethodReturnValueRequired) { continue; } $returnType = $this->methodsMapProcessor->getMethodReturnType($dataObjectType, $methodName); $key = $this->fieldNamer->getFieldNameForMethodName($methodName); if ($key === CustomAttributesDataInterface::CUSTOM_ATTRIBUTES && $value === []) { continue; } if ($key === CustomAttributesDataInterface::CUSTOM_ATTRIBUTES) { $value = $this->customAttributesProcessor->buildOutputDataArray($dataObject, $dataObjectType); } elseif ($key === "extension_attributes") { $value = $this->extensionAttributesProcessor->buildOutputDataArray($value, $returnType); } else { if (is_object($value) && !$value instanceof Phrase) { $value = $this->buildOutputDataArray($value, $returnType); } elseif (is_array($value)) { $valueResult = []; $arrayElementType = substr($returnType, 0, -2); foreach ($value as $singleValue) { if (is_object($singleValue) && !$singleValue instanceof Phrase) { $singleValue = $this->buildOutputDataArray($singleValue, $arrayElementType); } $valueResult[] = $this->typeCaster->castValueToType($singleValue, $arrayElementType); } $value = $valueResult; } else { $value = $this->typeCaster->castValueToType($value, $returnType); } } $outputData[$key] = $value; } return $outputData; }
/** * @param bool $isPermissionAllowed * @param array $expectedValue * @dataProvider buildOutputDataArrayWithPermissionProvider */ public function testBuildOutputDataArrayWithPermission($isPermissionAllowed, $expectedValue) { $dataObject = new \Magento\Framework\Reflection\Test\Unit\ExtensionAttributesObject(); $dataObjectType = 'Magento\\Framework\\Reflection\\Test\\Unit\\ExtensionAttributesObject'; $methodName = 'getAttrName'; $attributeName = 'attr_name'; $attributeValue = 'attrName'; $this->methodsMapProcessorMock->expects($this->once())->method('getMethodsMap')->with($dataObjectType)->will($this->returnValue([$methodName => []])); $this->methodsMapProcessorMock->expects($this->once())->method('isMethodValidForDataField')->with($dataObjectType, $methodName)->will($this->returnValue(true)); $this->fieldNamerMock->expects($this->once())->method('getFieldNameForMethodName')->with($methodName)->will($this->returnValue($attributeName)); $permissionName = 'Magento_Permission'; $this->configReaderMock->expects($this->once())->method('read')->will($this->returnValue([$dataObjectType => [$attributeName => [Converter::RESOURCE_PERMISSIONS => [$permissionName]]]])); $this->authorizationMock->expects($this->once())->method('isAllowed')->with($permissionName)->will($this->returnValue($isPermissionAllowed)); if ($isPermissionAllowed) { $this->methodsMapProcessorMock->expects($this->once())->method('getMethodReturnType')->with($dataObjectType, $methodName)->will($this->returnValue('string')); $this->typeCasterMock->expects($this->once())->method('castValueToType')->with($attributeValue, 'string')->will($this->returnValue($attributeValue)); } $value = $this->model->buildOutputDataArray($dataObject, $dataObjectType); $this->assertEquals($value, $expectedValue); }
/** * @param mixed $dataObject * @param string $getterMethodName * @param string $methodName * @param array $value * @param string $interfaceName * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function setComplexValue($dataObject, $getterMethodName, $methodName, array $value, $interfaceName) { if ($interfaceName == null) { $interfaceName = get_class($dataObject); } $returnType = $this->methodsMapProcessor->getMethodReturnType($interfaceName, $getterMethodName); if ($this->typeProcessor->isTypeSimple($returnType)) { $dataObject->{$methodName}($value); return $this; } if ($this->typeProcessor->isArrayType($returnType)) { $type = $this->typeProcessor->getArrayItemType($returnType); $objects = []; foreach ($value as $arrayElementData) { $object = $this->objectFactory->create($type, []); $this->populateWithArray($object, $arrayElementData, $type); $objects[] = $object; } $dataObject->{$methodName}($objects); return $this; } if (is_subclass_of($returnType, '\\Magento\\Framework\\Api\\ExtensibleDataInterface')) { $object = $this->objectFactory->create($returnType, []); $this->populateWithArray($object, $value, $returnType); } else { if (is_subclass_of($returnType, '\\Magento\\Framework\\Api\\ExtensionAttributesInterface')) { foreach ($value as $extensionAttributeKey => $extensionAttributeValue) { $extensionAttributeGetterMethodName = 'get' . \Magento\Framework\Api\SimpleDataObjectConverter::snakeCaseToUpperCamelCase($extensionAttributeKey); $methodReturnType = $this->methodsMapProcessor->getMethodReturnType($returnType, $extensionAttributeGetterMethodName); $extensionAttributeType = $this->typeProcessor->isArrayType($methodReturnType) ? $this->typeProcessor->getArrayItemType($methodReturnType) : $methodReturnType; if ($this->typeProcessor->isTypeSimple($extensionAttributeType)) { $value[$extensionAttributeKey] = $extensionAttributeValue; } else { if ($this->typeProcessor->isArrayType($methodReturnType)) { foreach ($extensionAttributeValue as $key => $extensionAttributeArrayValue) { $extensionAttribute = $this->objectFactory->create($extensionAttributeType, []); $this->populateWithArray($extensionAttribute, $extensionAttributeArrayValue, $extensionAttributeType); $value[$extensionAttributeKey][$key] = $extensionAttribute; } } else { $value[$extensionAttributeKey] = $this->objectFactory->create($extensionAttributeType, ['data' => $extensionAttributeValue]); } } } $object = $this->extensionFactory->create(get_class($dataObject), ['data' => $value]); } else { $object = $this->objectFactory->create($returnType, $value); } } $dataObject->{$methodName}($object); return $this; }
/** * Ensure that specified type is either a simple type or a valid service data type. * * @param string $typeName * @return $this * @throws \Exception In case when type is invalid */ protected function validateType($typeName) { if ($this->typeProcessor->isTypeSimple($typeName)) { return $this; } if ($this->typeProcessor->isArrayType($typeName)) { $arrayItemType = $this->typeProcessor->getArrayItemType($typeName); $this->methodsMap->getMethodsMap($arrayItemType); } else { $this->methodsMap->getMethodsMap($typeName); } return $this; }
/** * Writes out the extension attributes in an array. * * @param ExtensionAttributeInterface $dataObject * @param string $dataObjectType * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function buildOutputDataArray(ExtensionAttributesInterface $dataObject, $dataObjectType) { $methods = $this->methodsMapProcessor->getMethodsMap($dataObjectType); $outputData = []; /** @var MethodReflection $method */ foreach (array_keys($methods) as $methodName) { if (!$this->methodsMapProcessor->isMethodValidForDataField($dataObjectType, $methodName)) { continue; } $key = $this->fieldNamer->getFieldNameForMethodName($methodName); if ($this->isPermissionChecked && !$this->isAttributePermissionValid($dataObjectType, $key)) { continue; } $value = $dataObject->{$methodName}(); if ($value === null) { // all extension attributes are optional so don't need to check if isRequired continue; } $returnType = $this->methodsMapProcessor->getMethodReturnType($dataObjectType, $methodName); if (is_object($value) && !$value instanceof Phrase) { $value = $this->dataObjectProcessor->buildOutputDataArray($value, $returnType); } elseif (is_array($value)) { $valueResult = []; $arrayElementType = substr($returnType, 0, -2); foreach ($value as $singleValue) { if (is_object($singleValue) && !$singleValue instanceof Phrase) { $singleValue = $this->dataObjectProcessor->buildOutputDataArray($singleValue, $arrayElementType); } $valueResult[] = $this->typeCaster->castValueToType($singleValue, $arrayElementType); } $value = $valueResult; } else { $value = $this->typeCaster->castValueToType($value, $returnType); } $outputData[$key] = $value; } return $outputData; }
/** * Converts the incoming data into scalar or an array of scalars format. * * If the data provided is null, then an empty array is returned. Otherwise, if the data is an object, it is * assumed to be a Data Object and converted to an associative array with keys representing the properties of the * Data Object. * Nested Data Objects are also converted. If the data provided is itself an array, then we iterate through the * contents and convert each piece individually. * * @param mixed $data * @param string $serviceClassName * @param string $serviceMethodName * @return array|int|string|bool|float Scalar or array of scalars */ public function process($data, $serviceClassName, $serviceMethodName) { /** @var string $dataType */ $dataType = $this->methodsMapProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); if (is_array($data)) { $result = []; $arrayElementType = substr($dataType, 0, -2); foreach ($data as $datum) { if (is_object($datum)) { $datum = $this->processDataObject($this->dataObjectProcessor->buildOutputDataArray($datum, $arrayElementType)); } $result[] = $datum; } return $result; } elseif (is_object($data)) { return $this->processDataObject($this->dataObjectProcessor->buildOutputDataArray($data, $dataType)); } elseif ($data === null) { return []; } else { /** No processing is required for scalar types */ return $data; } }
/** * Get message schema defined by service method signature. * * @param \DOMNode $topicNode * @return array */ protected function extractSchemaDefinedByServiceMethod($topicNode) { $topicAttributes = $topicNode->attributes; if (!$topicAttributes->getNamedItem('schema')) { return null; } $topicName = $topicAttributes->getNamedItem('name')->nodeValue; list($className, $methodName) = $this->parseServiceMethod($topicAttributes->getNamedItem('schema')->nodeValue, $topicName); $result = [Config::SCHEMA_METHOD_PARAMS => [], Config::SCHEMA_METHOD_RETURN_TYPE => $this->methodsMap->getMethodReturnType($className, $methodName), Config::SCHEMA_METHOD_HANDLER => [Config::HANDLER_TYPE => $className, Config::HANDLER_METHOD => $methodName]]; $paramsMeta = $this->methodsMap->getMethodParams($className, $methodName); foreach ($paramsMeta as $paramPosition => $paramMeta) { $result[Config::SCHEMA_METHOD_PARAMS][] = [Config::SCHEMA_METHOD_PARAM_NAME => $paramMeta[MethodsMap::METHOD_META_NAME], Config::SCHEMA_METHOD_PARAM_POSITION => $paramPosition, Config::SCHEMA_METHOD_PARAM_IS_REQUIRED => !$paramMeta[MethodsMap::METHOD_META_HAS_DEFAULT_VALUE], Config::SCHEMA_METHOD_PARAM_TYPE => $paramMeta[MethodsMap::METHOD_META_TYPE]]; } return $result; }
/** * @param array $data1 * @param array $data2 * @dataProvider dataProviderForTestMergeDataObjects */ public function testMergeDataObjects($data1, $data2) { /** @var \Magento\Customer\Model\Data\Address $addressDataObject */ $firstAddressDataObject = $this->objectManager->getObject('Magento\\Customer\\Model\\Data\\Address', ['dataObjectHelper' => $this->dataObjectHelper]); /** @var \Magento\Customer\Model\Data\Region $regionDataObject */ $firstRegionDataObject = $this->objectManager->getObject('Magento\\Customer\\Model\\Data\\Region', ['dataObjectHelper' => $this->dataObjectHelper]); $firstRegionDataObject->setRegionId($data1['region']['region_id']); $firstRegionDataObject->setRegion($data1['region']['region']); if (isset($data1['id'])) { $firstAddressDataObject->setId($data1['id']); } if (isset($data1['country_id'])) { $firstAddressDataObject->setCountryId($data1['country_id']); } $firstAddressDataObject->setStreet($data1['street']); $firstAddressDataObject->setIsDefaultShipping($data1['default_shipping']); $firstAddressDataObject->setRegion($firstRegionDataObject); $secondAddressDataObject = $this->objectManager->getObject('Magento\\Customer\\Model\\Data\\Address', ['dataObjectHelper' => $this->dataObjectHelper]); /** @var \Magento\Customer\Model\Data\Region $regionDataObject */ $secondRegionDataObject = $this->objectManager->getObject('Magento\\Customer\\Model\\Data\\Region', ['dataObjectHelper' => $this->dataObjectHelper]); $secondRegionDataObject->setRegionId($data2['region']['region_id']); $secondRegionDataObject->setRegion($data2['region']['region']); if (isset($data2['id'])) { $secondAddressDataObject->setId($data2['id']); } if (isset($data2['country_id'])) { $secondAddressDataObject->setCountryId($data2['country_id']); } $secondAddressDataObject->setStreet($data2['street']); $secondAddressDataObject->setIsDefaultShipping($data2['default_shipping']); $secondAddressDataObject->setRegion($secondRegionDataObject); $this->objectProcessorMock->expects($this->once())->method('buildOutputDataArray')->with($secondAddressDataObject, get_class($firstAddressDataObject))->willReturn($data2); $this->methodsMapProcessor->expects($this->at(0))->method('getMethodReturnType')->with('Magento\\Customer\\Model\\Data\\Address', 'getStreet')->willReturn('string[]'); $this->methodsMapProcessor->expects($this->at(1))->method('getMethodReturnType')->with('Magento\\Customer\\Model\\Data\\Address', 'getRegion')->willReturn('\\Magento\\Customer\\Api\\Data\\RegionInterface'); $this->objectFactoryMock->expects($this->once())->method('create')->with('\\Magento\\Customer\\Api\\Data\\RegionInterface', [])->willReturn($secondRegionDataObject); $this->dataObjectHelper->mergeDataObjects(get_class($firstAddressDataObject), $firstAddressDataObject, $secondAddressDataObject); $this->assertSame($firstAddressDataObject->getId(), $secondAddressDataObject->getId()); $this->assertSame($firstAddressDataObject->getCountryId(), $secondAddressDataObject->getCountryId()); $this->assertSame($firstAddressDataObject->getStreet(), $secondAddressDataObject->getStreet()); $this->assertSame($firstAddressDataObject->isDefaultShipping(), $secondAddressDataObject->isDefaultShipping()); $this->assertSame($firstAddressDataObject->getRegion(), $secondAddressDataObject->getRegion()); }
/** * Convert service response into format acceptable by SoapServer. * * @param object|array|string|int|float|null $data * @param string $serviceClassName * @param string $serviceMethodName * @return array * @throws \InvalidArgumentException */ protected function _prepareResponseData($data, $serviceClassName, $serviceMethodName) { /** @var string $dataType */ $dataType = $this->methodsMapProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); $result = null; if (is_object($data)) { $result = $this->_dataObjectConverter->convertKeysToCamelCase($this->_dataObjectProcessor->buildOutputDataArray($data, $dataType)); } elseif (is_array($data)) { $dataType = substr($dataType, 0, -2); foreach ($data as $key => $value) { if ($value instanceof ExtensibleDataInterface || $value instanceof MetadataObjectInterface) { $result[] = $this->_dataObjectConverter->convertKeysToCamelCase($this->_dataObjectProcessor->buildOutputDataArray($value, $dataType)); } else { $result[$key] = $value; } } } elseif (is_scalar($data) || $data === null) { $result = $data; } else { throw new \InvalidArgumentException("Service returned result in invalid format."); } return [self::RESULT_NODE_NAME => $result]; }
/** * @param string $type * @param string $methodName * @param bool $expectedResult * @dataProvider isMethodReturnValueRequiredProvider */ public function testIsMethodReturnValueRequired($type, $methodName, $expectedResult) { $this->assertEquals($this->model->isMethodValidForDataField($type, $methodName), $expectedResult); }
/** * Converts the incoming data into scalar or an array of scalars format. * * If the data provided is null, then an empty array is returned. Otherwise, if the data is an object, it is * assumed to be a Data Object and converted to an associative array with keys representing the properties of the * Data Object. * Nested Data Objects are also converted. If the data provided is itself an array, then we iterate through the * contents and convert each piece individually. * * @param mixed $data * @param string $serviceClassName * @param string $serviceMethodName * @return array|int|string|bool|float Scalar or array of scalars */ public function process($data, $serviceClassName, $serviceMethodName) { /** @var string $dataType */ $dataType = $this->methodsMapProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); return $this->convertValue($data, $dataType); }