diff --git a/app/code/Magento/Backend/Block/Menu.php b/app/code/Magento/Backend/Block/Menu.php index 55ad3f2cbca00cec0f08004c0a0db5867727d769..087fd1fa351709778cc536f519c4b84206d97134 100644 --- a/app/code/Magento/Backend/Block/Menu.php +++ b/app/code/Magento/Backend/Block/Menu.php @@ -436,6 +436,7 @@ class Menu extends \Magento\Backend\Block\Template * @param array $colBrakes * @return string HTML * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function renderNavigation($menu, $level = 0, $limit = 0, $colBrakes = []) { @@ -454,21 +455,30 @@ class Menu extends \Magento\Backend\Block\Template } $id = $this->getJsId($menuItem->getId()); - $output .= '<li ' . $this->getUiId( - $menuItem->getId() - ) . ' class="item-' . $itemClass . ' ' . $this->_renderItemCssClass( - $menuItem, - $level - ) . ($level == 0 ? '" id="' . $id . '" aria-haspopup="true' : '') - . '" role="menu-item">' . $this->_renderAnchor( - $menuItem, - $level - ) . $this->_addSubMenu( - $menuItem, - $level, - $limit, - $id - ) . '</li>'; + if (count($menu) > 1 || $level != 1) { + $output .= '<li ' . $this->getUiId( + $menuItem->getId() + ) . ' class="item-' . $itemClass . ' ' . $this->_renderItemCssClass( + $menuItem, + $level + ) . ($level == 0 ? '" id="' . $id . '" aria-haspopup="true' : '') + . '" role="menu-item">' . $this->_renderAnchor( + $menuItem, + $level + ) . $this->_addSubMenu( + $menuItem, + $level, + $limit, + $id + ) . '</li>'; + } else { + $output .= $this->_addSubMenu( + $menuItem, + $level, + $limit, + $id); + } + $itemPosition++; } diff --git a/app/code/Magento/Bundle/etc/data_object.xml b/app/code/Magento/Bundle/etc/service_data_attributes.xml similarity index 70% rename from app/code/Magento/Bundle/etc/data_object.xml rename to app/code/Magento/Bundle/etc/service_data_attributes.xml index 2b3da013978f971bb20baca3a5e0ca95f2b501fc..8150a2dceeddd7d70d4e16ffa3d1b6996f316d5d 100644 --- a/app/code/Magento/Bundle/etc/data_object.xml +++ b/app/code/Magento/Bundle/etc/service_data_attributes.xml @@ -5,8 +5,8 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Catalog\Api\Data\ProductInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> <attribute code="bundle_product_options" type="Magento\Bundle\Api\Data\OptionInterface[]" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/app/code/Magento/CatalogInventory/etc/service_data_attributes.xml b/app/code/Magento/CatalogInventory/etc/service_data_attributes.xml new file mode 100644 index 0000000000000000000000000000000000000000..577b4678c42e99ad5d250f34409c2c5e51654848 --- /dev/null +++ b/app/code/Magento/CatalogInventory/etc/service_data_attributes.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> + <!-- + Once this is merged with the branch with the CatalogInventory integration, need to make sure the permission + below is actually used. + <attribute code="stock_item" type="Magento\CatalogInventory\Api\Data\StockItemInterface"> + <resources> + <resource ref="Magento_CatalogInventory::cataloginventory"/> + </resources> + </attribute> + --> + </extension_attributes> +</config> diff --git a/app/code/Magento/GiftMessage/etc/data_object.xml b/app/code/Magento/GiftMessage/etc/service_data_attributes.xml similarity index 64% rename from app/code/Magento/GiftMessage/etc/data_object.xml rename to app/code/Magento/GiftMessage/etc/service_data_attributes.xml index a1255cc8a161a5a8d28090697088a87ede8920e7..f02c0a717e169224d0b7d450e861f38d7176dc7f 100644 --- a/app/code/Magento/GiftMessage/etc/data_object.xml +++ b/app/code/Magento/GiftMessage/etc/service_data_attributes.xml @@ -5,11 +5,11 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Sales\Api\Data\OrderInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> <attribute code="gift_message" type="Magento\GiftMessage\Api\Data\MessageInterface" /> - </custom_attributes> - <custom_attributes for="Magento\Sales\Api\Data\OrderItemInterface"> + </extension_attributes> + <extension_attributes for="Magento\Sales\Api\Data\OrderItemInterface"> <attribute code="gift_message" type="Magento\GiftMessage\Api\Data\MessageInterface" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/app/code/Magento/GroupedProduct/etc/data_object.xml b/app/code/Magento/GroupedProduct/etc/service_data_attributes.xml similarity index 66% rename from app/code/Magento/GroupedProduct/etc/data_object.xml rename to app/code/Magento/GroupedProduct/etc/service_data_attributes.xml index d6a76ebaa081f2e742c78e66d70dc504c460bdde..0618cc9286cfd8ccb0cab46f6a505b05a39c4ee2 100644 --- a/app/code/Magento/GroupedProduct/etc/data_object.xml +++ b/app/code/Magento/GroupedProduct/etc/service_data_attributes.xml @@ -5,8 +5,8 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Catalog\Api\Data\ProductLinkInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Catalog\Api\Data\ProductLinkInterface"> <attribute code="qty" type="float" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/app/code/Magento/Sales/etc/data_object.xml b/app/code/Magento/Sales/etc/service_data_attributes.xml similarity index 78% rename from app/code/Magento/Sales/etc/data_object.xml rename to app/code/Magento/Sales/etc/service_data_attributes.xml index 8ef8080db8a29120f255cedfe0c083f45d85e5e5..1e7f320c18ed6e98b0c979d23e80e6ff48478fa4 100644 --- a/app/code/Magento/Sales/etc/data_object.xml +++ b/app/code/Magento/Sales/etc/service_data_attributes.xml @@ -5,10 +5,10 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Sales\Api\Data\OrderInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> <attribute code="applied_taxes" type="Magento\Tax\Api\Data\OrderTaxDetailsAppliedTaxInterface[]" /> <attribute code="item_applied_taxes" type="Magento\Tax\Api\Data\OrderTaxDetailsItemInterface[]" /> <attribute code="converting_from_quote" type="boolean" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/app/code/Magento/Webapi/Controller/Rest/Router/Route.php b/app/code/Magento/Webapi/Controller/Rest/Router/Route.php index 5c125c29611d9e2000436a7147329dfa449ab729..03e6d2fd8ca06eb864bfa318af4a91182e8c1bb7 100644 --- a/app/code/Magento/Webapi/Controller/Rest/Router/Route.php +++ b/app/code/Magento/Webapi/Controller/Rest/Router/Route.php @@ -69,7 +69,7 @@ class Route implements RouterInterface $this->variables[$key] = substr($value, 1); $value = null; } - $result[$key] = strtolower($value); + $result[$key] = $value; } return $result; } @@ -92,19 +92,12 @@ class Route implements RouterInterface /** * Retrieve unified requested path * - * Lowercase all path chunks, except variables names. - * E.g. the path '/V1/Categories/:categoryId' will be converted to '/v1/categories/:categoryId'. - * * @param string $path * @return array */ protected function getPathParts($path) { - $result = explode('/', trim($path, '/')); - array_walk($result, function (&$item) { - $item = substr($item, 0, 1) === ":" ? $item : strtolower($item); - }); - return $result; + return explode('/', trim($path, '/')); } /** diff --git a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php index 25d196ce6a80461109ec3246bbe3a5bb895cfa22..ec73bd7236c104f5364b03433e83d8c622e12414 100644 --- a/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php +++ b/app/code/Magento/Webapi/Controller/Soap/Request/Handler.php @@ -15,6 +15,7 @@ use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Webapi\Controller\Soap\Request as SoapRequest; use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Webapi\Model\Soap\Config as SoapConfig; +use Magento\Framework\Reflection\MethodsMap; /** * Handler of requests to SOAP server. @@ -48,6 +49,9 @@ class Handler /** @var DataObjectProcessor */ protected $_dataObjectProcessor; + /** @var MethodsMap */ + protected $methodsMapProcessor; + /** * Initialize dependencies. * @@ -58,6 +62,7 @@ class Handler * @param SimpleDataObjectConverter $dataObjectConverter * @param ServiceInputProcessor $serviceInputProcessor * @param DataObjectProcessor $dataObjectProcessor + * @param MethodsMap $methodsMapProcessor */ public function __construct( SoapRequest $request, @@ -66,7 +71,8 @@ class Handler AuthorizationInterface $authorization, SimpleDataObjectConverter $dataObjectConverter, ServiceInputProcessor $serviceInputProcessor, - DataObjectProcessor $dataObjectProcessor + DataObjectProcessor $dataObjectProcessor, + MethodsMap $methodsMapProcessor ) { $this->_request = $request; $this->_objectManager = $objectManager; @@ -75,6 +81,7 @@ class Handler $this->_dataObjectConverter = $dataObjectConverter; $this->serviceInputProcessor = $serviceInputProcessor; $this->_dataObjectProcessor = $dataObjectProcessor; + $this->methodsMapProcessor = $methodsMapProcessor; } /** @@ -149,7 +156,7 @@ class Handler protected function _prepareResponseData($data, $serviceClassName, $serviceMethodName) { /** @var string $dataType */ - $dataType = $this->_dataObjectProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); + $dataType = $this->methodsMapProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); $result = null; if (is_object($data)) { $result = $this->_dataObjectConverter diff --git a/app/code/Magento/Webapi/Model/Rest/Config.php b/app/code/Magento/Webapi/Model/Rest/Config.php index dfee1d873c02158645f482b3fe7242ecc5636bfe..51d0e67d7803603ef4ff02e6db406f041166b2d9 100644 --- a/app/code/Magento/Webapi/Model/Rest/Config.php +++ b/app/code/Magento/Webapi/Model/Rest/Config.php @@ -67,7 +67,7 @@ class Config /** @var $route \Magento\Webapi\Controller\Rest\Router\Route */ $route = $this->_routeFactory->createRoute( 'Magento\Webapi\Controller\Rest\Router\Route', - $this->_formatRoutePath($routeData[self::KEY_ROUTE_PATH]) + $routeData[self::KEY_ROUTE_PATH] ); $route->setServiceClass($routeData[self::KEY_CLASS]) @@ -78,22 +78,6 @@ class Config return $route; } - /** - * Lowercase all parts of the given route path except for the path parameters. - * - * @param string $routePath The route path (e.g. '/V1/Categories/:categoryId') - * @return string The modified route path (e.g. '/v1/categories/:categoryId') - */ - protected function _formatRoutePath($routePath) - { - $routePathParts = explode('/', $routePath); - $pathParts = []; - foreach ($routePathParts as $pathPart) { - $pathParts[] = substr($pathPart, 0, 1) === ":" ? $pathPart : strtolower($pathPart); - } - return implode('/', $pathParts); - } - /** * Get service base URL * diff --git a/app/code/Magento/Webapi/Test/Unit/Controller/Rest/Router/RouteTest.php b/app/code/Magento/Webapi/Test/Unit/Controller/Rest/Router/RouteTest.php index 6732d012dc1e726e29e4afab132711759d4ce7f9..4a9192b36882b9f69a66755af08cd3f050374beb 100644 --- a/app/code/Magento/Webapi/Test/Unit/Controller/Rest/Router/RouteTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Controller/Rest/Router/RouteTest.php @@ -95,10 +95,7 @@ class RouteTest extends \PHPUnit_Framework_TestCase ['/V1/one/two/:threeValue/four/:fiveValue', '/V1/one/two/3/four/5', ['threeValue' => 3, 'fiveValue' => 5]], ['/v1/One', '/v1/One', []], - ['/v1/oNe', '/V1/one', []], - ['/v1/onE', '/V1/oNe', []], - ['/v1/One/:twoValue', '/V1/one/2', ['twoValue' => 2]], ['/v1/oNe/:TwoValue', '/v1/oNe/2', ['TwoValue' => 2]], ['/v1/onE/:twovalue', '/v1/onE/2', ['twovalue' => 2]], @@ -108,6 +105,9 @@ class RouteTest extends \PHPUnit_Framework_TestCase ['/V1/one-one/:two_value', '/V1/one-one/2', ['two_value' => 2]], // Error + ['/v1/oNe', '/V1/one', false], + ['/v1/onE', '/V1/oNe', false], + ['/v1/One/:twoValue', '/V1/one/2', false], ['/V1/one', '/V1/two', false], ['/V1/one/:twoValue', '/V1/one', false], ['/V1/one/two', '/V1/one', false], diff --git a/app/code/Magento/Webapi/Test/Unit/Controller/Soap/Request/HandlerTest.php b/app/code/Magento/Webapi/Test/Unit/Controller/Soap/Request/HandlerTest.php index 2800d5466585fdc8eec97ba82dd53d7f5fee0c93..8c3741c3b8d48c86e0e876125cee6558a2c3a403 100644 --- a/app/code/Magento/Webapi/Test/Unit/Controller/Soap/Request/HandlerTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Controller/Soap/Request/HandlerTest.php @@ -40,6 +40,9 @@ class HandlerTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Framework\Reflection\DataObjectProcessor|\PHPUnit_Framework_MockObject_MockObject */ protected $_dataObjectProcessorMock; + /** @var \Magento\Framework\Reflection\MethodsMap|\PHPUnit_Framework_MockObject_MockObject */ + protected $_methodsMapProcessorMock; + /** @var array */ protected $_arguments; @@ -67,7 +70,13 @@ class HandlerTest extends \PHPUnit_Framework_TestCase ); $this->_dataObjectProcessorMock = $this->getMock( 'Magento\Framework\Reflection\DataObjectProcessor', - ['getMethodReturnType'], + [], + [], + '', + false); + $this->_methodsMapProcessorMock = $this->getMock( + 'Magento\Framework\Reflection\MethodsMap', + [], [], '', false); @@ -80,7 +89,8 @@ class HandlerTest extends \PHPUnit_Framework_TestCase $this->_authorizationMock, $this->_dataObjectConverter, $this->_serviceInputProcessorMock, - $this->_dataObjectProcessorMock + $this->_dataObjectProcessorMock, + $this->_methodsMapProcessorMock ); parent::setUp(); } @@ -128,10 +138,6 @@ class HandlerTest extends \PHPUnit_Framework_TestCase ->method('process') ->will($this->returnArgument(2)); - $this->_dataObjectProcessorMock->expects($this->any())->method('getMethodReturnType') - ->with($className, $methodName) - ->will($this->returnValue('string')); - /** Execute SUT. */ $this->assertEquals( ['result' => $serviceResponse], diff --git a/app/code/Magento/Webapi/Test/Unit/Model/DataObjectProcessorTest.php b/app/code/Magento/Webapi/Test/Unit/Model/DataObjectProcessorTest.php index 3453c7e1d7951de030d96012f813477fe73efd4b..c3f586d835a8d3c3d1d85e3626e258680541441c 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/DataObjectProcessorTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Model/DataObjectProcessorTest.php @@ -23,7 +23,21 @@ class DataObjectProcessorTest extends \PHPUnit_Framework_TestCase protected function setup() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->dataObjectProcessor = $objectManager->getObject('Magento\Framework\Reflection\DataObjectProcessor'); + $methodsMapProcessor = $objectManager->getObject( + 'Magento\Framework\Reflection\MethodsMap', + [ + 'fieldNamer' => $objectManager->getObject('Magento\Framework\Reflection\FieldNamer'), + 'typeProcessor' => $objectManager->getObject('Magento\Framework\Reflection\TypeProcessor'), + ] + ); + $this->dataObjectProcessor = $objectManager->getObject( + 'Magento\Framework\Reflection\DataObjectProcessor', + [ + 'methodsMapProcessor' => $methodsMapProcessor, + 'typeCaster' => $objectManager->getObject('Magento\Framework\Reflection\TypeCaster'), + 'fieldNamer' => $objectManager->getObject('Magento\Framework\Reflection\FieldNamer'), + ] + ); parent::setUp(); } diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Files/TestDataInterface.php b/app/code/Magento/Webapi/Test/Unit/Model/Files/TestDataInterface.php index 1b4f971c2c3a447261a22ce6bb898311813e65b7..2010509ff416165858cbdcb1b1d3b3ae298c9759 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Files/TestDataInterface.php +++ b/app/code/Magento/Webapi/Test/Unit/Model/Files/TestDataInterface.php @@ -8,11 +8,23 @@ namespace Magento\Webapi\Test\Unit\Model\Files; interface TestDataInterface { + /** + * @return string + */ public function getId(); + /** + * @return string + */ public function getAddress(); + /** + * @return string + */ public function isDefaultShipping(); + /** + * @return string + */ public function isRequiredBilling(); } diff --git a/app/code/Magento/Webapi/etc/di.xml b/app/code/Magento/Webapi/etc/di.xml index 12b661f639c428c681b98b1df7c0f5a8131da632..c58acdbf51c4b9e3252745d425b3d365e2a60449 100644 --- a/app/code/Magento/Webapi/etc/di.xml +++ b/app/code/Magento/Webapi/etc/di.xml @@ -22,11 +22,17 @@ <type name="Magento\Framework\Xml\Parser" shared="false" /> <type name="Magento\Framework\Code\Scanner\DirectoryScanner" shared="false" /> <type name="Magento\Server\Reflection" shared="false" /> - <type name="Magento\Framework\Reflection\DataObjectProcessor"> + <type name="Magento\Framework\Reflection\MethodsMap"> <arguments> <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Webapi</argument> </arguments> </type> + <type name="Magento\Framework\Reflection\DataObjectProcessor"> + <arguments> + <argument name="extensionAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\ExtensionAttributesProcessor\Proxy</argument> + <argument name="customAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\CustomAttributesProcessor\Proxy</argument> + </arguments> + </type> <type name="Magento\Integration\Model\ConfigBasedIntegrationManager"> <plugin name="webapiSetup" type="Magento\Webapi\Model\Plugin\Manager" /> </type> diff --git a/app/code/Magento/Webapi/etc/webapi_rest/di.xml b/app/code/Magento/Webapi/etc/webapi_rest/di.xml index 48420045d4afca1644c159d3fa68a541cd8c403a..7941e76f564b0fa13986a9847e250a34f36d383c 100644 --- a/app/code/Magento/Webapi/etc/webapi_rest/di.xml +++ b/app/code/Magento/Webapi/etc/webapi_rest/di.xml @@ -71,4 +71,25 @@ <type name="Magento\Framework\Authorization"> <plugin name="guestAuthorization" type="Magento\Webapi\Model\Plugin\GuestAuthorization" /> </type> + + <!-- Configuration to check that the permissions are checked on fields --> + <virtualType name="Magento\Framework\Reflection\ExtensionAttributesProcessorPermissionChecked" type="Magento\Framework\Reflection\ExtensionAttributesProcessor"> + <arguments> + <argument name="isPermissionChecked" xsi:type="boolean">true</argument> + <argument name="dataObjectProcessor" xsi:type="object">Magento\Framework\Reflection\DataObjectProcessor\Proxy</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Framework\Reflection\DataObjectProcessorPermissionChecked" type="Magento\Framework\Reflection\DataObjectProcessor"> + <arguments> + <argument name="extensionAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\ExtensionAttributesProcessorPermissionChecked</argument> + <argument name="customAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\CustomAttributesProcessor\Proxy</argument> + </arguments> + </virtualType> + + <type name="Magento\Framework\Webapi\ServiceOutputProcessor"> + <arguments> + <argument name="dataObjectProcessor" xsi:type="object">Magento\Framework\Reflection\DataObjectProcessorPermissionChecked</argument> + </arguments> + </type> + <!-- End of configuration to check that permissions are checked on fields --> </config> diff --git a/app/code/Magento/Webapi/etc/webapi_soap/di.xml b/app/code/Magento/Webapi/etc/webapi_soap/di.xml index 6d5607c49c18dc46d9e6d9cbc429bc11f4a26467..c8cabfba0ac2529cb3365da99ae129086bee75fe 100644 --- a/app/code/Magento/Webapi/etc/webapi_soap/di.xml +++ b/app/code/Magento/Webapi/etc/webapi_soap/di.xml @@ -39,4 +39,26 @@ <type name="Magento\Framework\Authorization"> <plugin name="guestAuthorization" type="Magento\Webapi\Model\Plugin\GuestAuthorization" /> </type> + + <!-- Configuration to check that the permissions are checked on fields --> + <virtualType name="Magento\Framework\Reflection\ExtensionAttributesProcessorPermissionChecked" type="Magento\Framework\Reflection\ExtensionAttributesProcessor"> + <arguments> + <argument name="isPermissionChecked" xsi:type="boolean">true</argument> + <argument name="dataObjectProcessor" xsi:type="object">Magento\Framework\Reflection\DataObjectProcessor\Proxy</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Framework\Reflection\DataObjectProcessorPermissionChecked" type="Magento\Framework\Reflection\DataObjectProcessor"> + <arguments> + <argument name="extensionAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\ExtensionAttributesProcessorPermissionChecked</argument> + <argument name="customAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\CustomAttributesProcessor\Proxy</argument> + </arguments> + </virtualType> + + <type name="Magento\Webapi\Controller\Soap\Request\Handler"> + <arguments> + <argument name="dataObjectProcessor" xsi:type="object">Magento\Framework\Reflection\DataObjectProcessorPermissionChecked</argument> + </arguments> + </type> + <!-- End of configuration to check that permissions are checked on fields --> + </config> diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/data_object.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/service_data_attributes.xml similarity index 69% rename from dev/tests/api-functional/_files/Magento/TestModule1/etc/data_object.xml rename to dev/tests/api-functional/_files/Magento/TestModule1/etc/service_data_attributes.xml index de2236e2195f380aedd5614447065bc9e480f7aa..fa96514a063b6112004e7a6073a2cc131a87f44c 100644 --- a/dev/tests/api-functional/_files/Magento/TestModule1/etc/data_object.xml +++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/service_data_attributes.xml @@ -5,13 +5,13 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\TestModule1\Service\V1\Entity\Item"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\TestModule1\Service\V1\Entity\Item"> <attribute code="custom_attribute_data_object" type="Magento\TestModuleMSC\Model\Data\CustomAttributeDataObject" /> <attribute code="custom_attribute_string" type="string" /> - </custom_attributes> - <custom_attributes for="Magento\TestModuleMSC\Model\Data\CustomAttributeDataObject"> + </extension_attributes> + <extension_attributes for="Magento\TestModuleMSC\Model\Data\CustomAttributeDataObject"> <attribute code="custom_attribute_nested" type="Magento\TestModuleMSC\Model\Data\CustomAttributeNestedDataObject" /> <attribute code="custom_attribute_int" type="int" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/data_object.xml b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/service_data_attributes.xml similarity index 69% rename from dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/data_object.xml rename to dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/service_data_attributes.xml index ece2b1f581775daa25e1acec4f4fc120a876c431..f11e639ccd2753296d65071cf32f3e5fb47e0e7d 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/data_object.xml +++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/service_data_attributes.xml @@ -5,13 +5,13 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\TestModuleMSC\Api\Data\ItemInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\TestModuleMSC\Api\Data\ItemInterface"> <attribute code="custom_attribute_data_object" type="Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectInterface" /> <attribute code="custom_attribute_string" type="string" /> - </custom_attributes> - <custom_attributes for="Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectInterface"> + </extension_attributes> + <extension_attributes for="Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectInterface"> <attribute code="custom_attribute_nested" type="Magento\TestModuleMSC\Api\Data\CustomAttributeNestedDataObjectInterface" /> <attribute code="custom_attribute_int" type="int" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RestErrorHandlingTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RestErrorHandlingTest.php index 41d6e8a2914deb9a863b42d2634d0c9aab934324..a64744df17ad68e016255d6d2396bad4e59564f6 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RestErrorHandlingTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RestErrorHandlingTest.php @@ -81,7 +81,7 @@ class RestErrorHandlingTest extends \Magento\TestFramework\TestCase\WebapiAbstra { $serviceInfo = [ 'rest' => [ - 'resourcePath' => '/V1/errortest/otherexception', + 'resourcePath' => '/V1/errortest/otherException', 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, ], ]; diff --git a/dev/tests/integration/framework/bootstrap.php b/dev/tests/integration/framework/bootstrap.php index 41453aa0becc0e2d32513736f2cb99ddc60e8854..b71bb17d945ccd42f693ed57588116e05c474dbb 100644 --- a/dev/tests/integration/framework/bootstrap.php +++ b/dev/tests/integration/framework/bootstrap.php @@ -8,6 +8,11 @@ use Magento\Framework\Autoload\AutoloaderRegistry; require_once __DIR__ . '/../../../../app/bootstrap.php'; require_once __DIR__ . '/autoload.php'; +$updateAppBootstrap = __DIR__ . '/../../../../update/app/bootstrap.php'; +if (file_exists($updateAppBootstrap)) { + require_once $updateAppBootstrap; +} + $testsBaseDir = dirname(__DIR__); $testsTmpDir = "{$testsBaseDir}/tmp"; $magentoBaseDir = realpath("{$testsBaseDir}/../../../"); diff --git a/dev/tests/integration/phpunit.xml.dist b/dev/tests/integration/phpunit.xml.dist index 7a9e365c8f15056e5060cd27827720e2d18d2fce..bd6cbffb247d64f6617eef5fc9c8ebaebb753b7e 100644 --- a/dev/tests/integration/phpunit.xml.dist +++ b/dev/tests/integration/phpunit.xml.dist @@ -18,6 +18,7 @@ </testsuite> <testsuite name="Magento Integration Tests"> <directory suffix="Test.php">testsuite</directory> + <directory suffix="Test.php">../../../update/dev/tests/integration/testsuite</directory> <exclude>testsuite/Magento/Test/Integrity</exclude> <exclude>testsuite/Magento/MemoryUsageTest.php</exclude> </testsuite> @@ -27,6 +28,7 @@ <whitelist addUncoveredFilesFromWhiteList="true"> <directory suffix=".php">../../../app/code/Magento</directory> <directory suffix=".php">../../../lib/internal/Magento</directory> + <directory suffix=".php">../../../update/app/code</directory> </whitelist> </filter> <!-- PHP INI settings and constants definition --> diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/_files/menu/expected.txt b/dev/tests/integration/testsuite/Magento/Backend/Block/_files/menu/expected.txt index fc890c803aae34f8ca74f4c00a541415b426034a..dc07065f7be1e4b2db53584c769bb52abb79de21 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/_files/menu/expected.txt +++ b/dev/tests/integration/testsuite/Magento/Backend/Block/_files/menu/expected.txt @@ -1 +1 @@ -<ul id="nav" role="menubar" ><li data-ui-id="magento-backend-system" class="item-system parent last level-0" id="magento-backend-system" aria-haspopup="true" role="menu-item"><a href="#" onclick="return false;" class=""><span>System</span></a><div class="submenu" aria-labelledby="magento-backend-system"><ul role="menu" ><li data-ui-id="magento-backend-system-report" class="item-system-report parent level-1" role="menu-item"><strong class="submenu-group-title" role="presentation"><span>Report</span></strong><div class="submenu"><ul role="menu" ><li data-ui-id="magento-backend-system-report-private-sales" class="item-system-report-private-sales level-2" role="menu-item"><a href="#" onclick="return false;" class=""><span>Private Sales</span></a></li><li data-ui-id="magento-backend-system-report-magento-invite-general" class="item-system-report-magento-invite-general level-2" role="menu-item"><a href="#" onclick="return false;" class=""><span>Invite</span></a></li><li data-ui-id="magento-backend-system-report-magento-invite-customer" class="item-system-report-magento-invite-customer level-2" role="menu-item"><a href="#" onclick="return false;" class=""><span>Invited Customers</span></a></li></ul></div></li></ul></div></li></ul> \ No newline at end of file +<ul id="nav" role="menubar" ><li data-ui-id="magento-backend-system" class="item-system parent last level-0" id="magento-backend-system" aria-haspopup="true" role="menu-item"><a href="#" onclick="return false;" class=""><span>System</span></a><div class="submenu" aria-labelledby="magento-backend-system"><ul role="menu" ><div class="submenu"><ul role="menu" ><li data-ui-id="magento-backend-system-report-private-sales" class="item-system-report-private-sales level-2" role="menu-item"><a href="#" onclick="return false;" class=""><span>Private Sales</span></a></li><li data-ui-id="magento-backend-system-report-magento-invite-general" class="item-system-report-magento-invite-general level-2" role="menu-item"><a href="#" onclick="return false;" class=""><span>Invite</span></a></li><li data-ui-id="magento-backend-system-report-magento-invite-customer" class="item-system-report-magento-invite-customer level-2" role="menu-item"><a href="#" onclick="return false;" class=""><span>Invited Customers</span></a></li></ul></div></ul></div></li></ul> \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/etc/data_object.xml b/dev/tests/integration/testsuite/Magento/Catalog/_files/etc/service_data_attributes.xml similarity index 63% rename from dev/tests/integration/testsuite/Magento/Catalog/_files/etc/data_object.xml rename to dev/tests/integration/testsuite/Magento/Catalog/_files/etc/service_data_attributes.xml index c2888a64ee00c0e6f18a7d1126920e4c85a8b091..93cf7bfb484a6de773ab318549385ad2436f2fa4 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/etc/data_object.xml +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/etc/service_data_attributes.xml @@ -5,22 +5,22 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> - </custom_attributes> - <custom_attributes for="Magento\Catalog\Api\Data\ProductInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> + </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> <attribute code="stock_item" type="Magento\CatalogInventory\Service\Data\V1\StockItem" /> - </custom_attributes> - <custom_attributes for="Magento\Catalog\Api\Data\CategoryInterface"> + </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\CategoryInterface"> <attribute code="category_attribute_1" type="Magento\Catalog\Api\Data\CategoryAttributeType1" /> <attribute code="category_attribute_2" type="Magento\Catalog\Api\Data\CategoryAttributeType2" /> - </custom_attributes> - <custom_attributes for="Magento\Customer\Api\Data\CustomerInterface"> + </extension_attributes> + <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface"> <attribute code="customer_attribute_1" type="Magento\Customer\Api\Data\CustomerAttributeType1" /> <attribute code="customer_attribute_2" type="Magento\Customer\Api\Data\CustomerAttributeType2" /> - </custom_attributes> - <custom_attributes for="Magento\Customer\Api\Data\Address"> + </extension_attributes> + <extension_attributes for="Magento\Customer\Api\Data\Address"> <attribute code="address_attribute_1" type="Magento\Customer\Api\Data\AddressAttributeType1" /> <attribute code="address_attribute_2" type="Magento\Customer\Api\Data\AddressAttributeType2" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/FileResolverStub.php b/dev/tests/integration/testsuite/Magento/Customer/Model/FileResolverStub.php index 48d81d30eb84119c45e440be2e57bd48b4c3c8e9..9ee9ed911f7c092aac6034ed441e5c96f2e46aa2 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/FileResolverStub.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/FileResolverStub.php @@ -21,7 +21,7 @@ class FileResolverStub implements \Magento\Framework\Config\FileResolverInterfac 'path' => realpath(__DIR__ . '/../_files/etc'), ] ); - $paths = ['data_object.xml']; + $paths = ['service_data_attributes.xml']; return new \Magento\Framework\Config\FileIterator($readDirectory, $paths); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/etc/data_object.xml b/dev/tests/integration/testsuite/Magento/Customer/_files/etc/service_data_attributes.xml similarity index 63% rename from dev/tests/integration/testsuite/Magento/Customer/_files/etc/data_object.xml rename to dev/tests/integration/testsuite/Magento/Customer/_files/etc/service_data_attributes.xml index a75fa2a653213ade29aa7ea81e8482e752ed9210..a7ae041eda7683ce99dd168befc50c2ae5a46f3c 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/etc/data_object.xml +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/etc/service_data_attributes.xml @@ -5,22 +5,22 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> - </custom_attributes> - <custom_attributes for="Magento\Catalog\Api\Data\ProductInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> + </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> <attribute code="stock_item" type="Magento\CatalogInventory\Service\Data\V1\StockItem" /> - </custom_attributes> - <custom_attributes for="Magento\Catalog\Api\Data\CategoryInterface"> + </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\CategoryInterface"> <attribute code="category_attribute_1" type="Magento\Catalog\Api\Data\CategoryAttributeType1" /> <attribute code="category_attribute_2" type="Magento\Catalog\Api\Data\CategoryAttributeType2" /> - </custom_attributes> - <custom_attributes for="Magento\Customer\Api\Data\CustomerInterface"> + </extension_attributes> + <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface"> <attribute code="customer_attribute_1" type="Magento\Customer\Api\Data\CustomerAttributeType1" /> <attribute code="customer_attribute_2" type="Magento\Customer\Api\Data\CustomerAttributeType2" /> - </custom_attributes> - <custom_attributes for="Magento\Customer\Api\Data\AddressInterface"> + </extension_attributes> + <extension_attributes for="Magento\Customer\Api\Data\AddressInterface"> <attribute code="address_attribute_1" type="Magento\Customer\Api\Data\AddressAttributeType1" /> <attribute code="address_attribute_2" type="Magento\Customer\Api\Data\AddressAttributeType2" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/dev/tests/integration/testsuite/Magento/Framework/Api/Config/ReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/Api/Config/ReaderTest.php index 351a0ba03144a218481d9ab798cca18989ca8db8..07fceddb6424b3bc01d7e4448833cb92a709d6c9 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Api/Config/ReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Api/Config/ReaderTest.php @@ -76,12 +76,24 @@ class ReaderTest extends \PHPUnit_Framework_TestCase $expectedArray = [ 'Magento\Tax\Api\Data\TaxRateInterface' => [], 'Magento\Catalog\Api\Data\Product' => [ - 'stock_item' => "Magento\CatalogInventory\Api\Data\StockItem", + 'stock_item' => [ + "type" => "Magento\CatalogInventory\Api\Data\StockItem", + "resourceRefs" => [], + ], ], 'Magento\Customer\Api\Data\CustomerInterface' => [ - 'custom_1' => "Magento\Customer\Api\Data\CustomerCustom", - 'custom_2' => "Magento\CustomerExtra\Api\Data\CustomerCustom22", - 'custom_3' => "Magento\Customer\Api\Data\CustomerCustom3", + 'custom_1' => [ + "type" => "Magento\Customer\Api\Data\CustomerCustom", + "resourceRefs" => [], + ], + 'custom_2' => [ + "type" => "Magento\CustomerExtra\Api\Data\CustomerCustom22", + "resourceRefs" => [], + ], + 'custom_3' => [ + "type" => "Magento\Customer\Api\Data\CustomerCustom3", + "resourceRefs" => [], + ], ], ]; diff --git a/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_one.xml b/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_one.xml index f5a3d522454ab487f461f697e6108f118f1769e9..82aa641d5e8bd9040d9fa26689d95a09b74e19f3 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_one.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_one.xml @@ -5,14 +5,14 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> - </custom_attributes> - <custom_attributes for="Magento\Catalog\Api\Data\Product"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> + </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\Product"> <attribute code="stock_item" type="Magento\CatalogInventory\Api\Data\StockItem" /> - </custom_attributes> - <custom_attributes for="Magento\Customer\Api\Data\CustomerInterface"> + </extension_attributes> + <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface"> <attribute code="custom_1" type="Magento\Customer\Api\Data\CustomerCustom" /> <attribute code="custom_2" type="Magento\CustomerExtra\Service\V1\Data\CustomerCustom21" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_two.xml b/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_two.xml index 629efb9b7a30a400dad479894f71d0711da8f24d..5596f61e572f4b076a1d850b38002b6a448a7297 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_two.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/Api/Config/_files/config_two.xml @@ -5,9 +5,9 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Customer\Api\Data\CustomerInterface"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface"> <attribute code="custom_2" type="Magento\CustomerExtra\Api\Data\CustomerCustom22" /> <attribute code="custom_3" type="Magento\Customer\Api\Data\CustomerCustom3" /> - </custom_attributes> + </extension_attributes> </config> diff --git a/dev/tests/unit/framework/bootstrap.php b/dev/tests/unit/framework/bootstrap.php index b30486b50a17dc3a6e0b67adaa072868b1747736..45ae70bc21fbb39fc79b347cc4749a6243d18d7f 100755 --- a/dev/tests/unit/framework/bootstrap.php +++ b/dev/tests/unit/framework/bootstrap.php @@ -6,6 +6,11 @@ require_once __DIR__ . '/../../../../app/autoload.php'; +$updateAppBootstrap = __DIR__ . '/../../../../update/app/bootstrap.php'; +if (file_exists($updateAppBootstrap)) { + require_once $updateAppBootstrap; +} + if (!defined('TESTS_TEMP_DIR')) { define('TESTS_TEMP_DIR', dirname(__DIR__) . '/tmp'); } diff --git a/dev/tests/unit/phpunit.xml.dist b/dev/tests/unit/phpunit.xml.dist index 5c785e252e48aa8c82aa8bcfe817c761b38ecc23..4c5587187da794847e7e0e6c47157106193a14fd 100644 --- a/dev/tests/unit/phpunit.xml.dist +++ b/dev/tests/unit/phpunit.xml.dist @@ -17,6 +17,7 @@ <directory suffix="Test.php">../../../lib/internal/*/*/Test/Unit</directory> <directory suffix="Test.php">../../../lib/internal/*/*/*/Test/Unit</directory> <directory suffix="Test.php">../../../setup/src/*/*/Test/Unit</directory> + <directory suffix="Test.php">../../../update/dev/tests/unit/testsuite</directory> </testsuite> <php> <ini name="date.timezone" value="America/Los_Angeles"/> @@ -26,6 +27,7 @@ <directory suffix=".php">../../../app/code/*</directory> <directory suffix=".php">../../../lib/internal/Magento</directory> <directory suffix=".php">../../../setup/src/*</directory> + <directory suffix=".php">../../../update/app/code/*</directory> <exclude> <directory>../../../app/code/*/*/Test</directory> <directory>../../../lib/internal/*/*/Test</directory> diff --git a/lib/internal/Magento/Framework/Api/Code/Generator/ExtensionAttributesGenerator.php b/lib/internal/Magento/Framework/Api/Code/Generator/ExtensionAttributesGenerator.php index 98f5dc5d5e07250fe7b427ca68286cb99c22ee35..aa91250b41fb3ce83f95c8d3df94e144c79a9bfb 100644 --- a/lib/internal/Magento/Framework/Api/Code/Generator/ExtensionAttributesGenerator.php +++ b/lib/internal/Magento/Framework/Api/Code/Generator/ExtensionAttributesGenerator.php @@ -8,6 +8,7 @@ namespace Magento\Framework\Api\Code\Generator; use Magento\Framework\Code\Generator\DefinedClasses; use Magento\Framework\Code\Generator\Io; use Magento\Framework\Api\SimpleDataObjectConverter; +use Magento\Framework\Api\Config\Converter; /** * Code generator for data object extensions. @@ -79,14 +80,15 @@ class ExtensionAttributesGenerator extends \Magento\Framework\Code\Generator\Ent protected function _getClassMethods() { $methods = []; - foreach ($this->getCustomAttributes() as $attributeName => $attributeType) { + foreach ($this->getCustomAttributes() as $attributeName => $attributeMetadata) { + $attributeType = $attributeMetadata[Converter::DATA_TYPE]; $propertyName = SimpleDataObjectConverter::snakeCaseToCamelCase($attributeName); $getterName = 'get' . ucfirst($propertyName); $setterName = 'set' . ucfirst($propertyName); $methods[] = [ 'name' => $getterName, 'body' => "return \$this->_get('{$attributeName}');", - 'docblock' => ['tags' => [['name' => 'return', 'description' => $attributeType]]], + 'docblock' => ['tags' => [['name' => 'return', 'description' => $attributeType . '|null']]], ]; $methods[] = [ 'name' => $setterName, @@ -150,11 +152,12 @@ class ExtensionAttributesGenerator extends \Magento\Framework\Code\Generator\Ent } $dataInterface = ltrim($this->getSourceClassName(), '\\'); if (isset($this->allCustomAttributes[$dataInterface])) { - foreach ($this->allCustomAttributes[$dataInterface] as $attributeName => $attributeType) { + foreach ($this->allCustomAttributes[$dataInterface] as $attributeName => $attributeMetadata) { + $attributeType = $attributeMetadata[Converter::DATA_TYPE]; if (strpos($attributeType, '\\') !== false) { /** Add preceding slash to class names, while leaving primitive types as is */ $attributeType = $this->_getFullyQualifiedClassName($attributeType); - $this->allCustomAttributes[$dataInterface][$attributeName] = + $this->allCustomAttributes[$dataInterface][$attributeName][Converter::DATA_TYPE] = $this->_getFullyQualifiedClassName($attributeType); } } diff --git a/lib/internal/Magento/Framework/Api/Config/Converter.php b/lib/internal/Magento/Framework/Api/Config/Converter.php index f21db3bbd9b224b1c29eaa36be3f6043dcdf4ae9..68914a98b5c77ab1bc452c90fc26dfbee64797d8 100644 --- a/lib/internal/Magento/Framework/Api/Config/Converter.php +++ b/lib/internal/Magento/Framework/Api/Config/Converter.php @@ -7,6 +7,9 @@ namespace Magento\Framework\Api\Config; class Converter implements \Magento\Framework\Config\ConverterInterface { + const RESOURCE_PERMISSIONS = "resourceRefs"; + const DATA_TYPE = "type"; + /** * Convert dom node tree to array * @@ -21,7 +24,7 @@ class Converter implements \Magento\Framework\Config\ConverterInterface } /** @var \DOMNodeList $types */ - $types = $source->getElementsByTagName('custom_attributes'); + $types = $source->getElementsByTagName('extension_attributes'); /** @var \DOMNode $type */ foreach ($types as $type) { $typeConfig = []; @@ -32,9 +35,22 @@ class Converter implements \Magento\Framework\Config\ConverterInterface $code = $attribute->getAttribute('code'); $codeType = $attribute->getAttribute('type'); - if ($code && $codeType) { - $typeConfig[$code] = $codeType; + $resourcesElement = $attribute->getElementsByTagName('resources')->item(0); + $resourceRefs = []; + if ($resourcesElement && $resourcesElement->nodeType === XML_ELEMENT_NODE) { + $singleResourceElements = $resourcesElement->getElementsByTagName('resource'); + foreach ($singleResourceElements as $element) { + if ($element->nodeType != XML_ELEMENT_NODE) { + continue; + } + $resourceRefs[] = $element->attributes->getNamedItem('ref')->nodeValue; + } } + + $typeConfig[$code] = [ + self::DATA_TYPE => $codeType, + self::RESOURCE_PERMISSIONS => $resourceRefs, + ]; } $output[$typeName] = $typeConfig; diff --git a/lib/internal/Magento/Framework/Api/Config/Reader.php b/lib/internal/Magento/Framework/Api/Config/Reader.php index 8cd1a8ac3293568553c30f0e126ab1ee61b78a38..6bc97c71c11563cfb46efeaaedf2ecd03fd3bf83 100644 --- a/lib/internal/Magento/Framework/Api/Config/Reader.php +++ b/lib/internal/Magento/Framework/Api/Config/Reader.php @@ -13,8 +13,8 @@ class Reader extends \Magento\Framework\Config\Reader\Filesystem * @var array */ protected $_idAttributes = [ - '/config/custom_attributes' => 'for', - '/config/custom_attributes/attribute' => 'code', + '/config/extension_attributes' => 'for', + '/config/extension_attributes/attribute' => 'code', ]; /** @@ -32,7 +32,7 @@ class Reader extends \Magento\Framework\Config\Reader\Filesystem \Magento\Framework\Api\Config\Converter $converter, \Magento\Framework\Api\Config\SchemaLocator $schemaLocator, \Magento\Framework\Config\ValidationStateInterface $validationState, - $fileName = 'data_object.xml', + $fileName = 'service_data_attributes.xml', $idAttributes = [], $domDocumentClass = 'Magento\Framework\Config\Dom', $defaultScope = 'global' diff --git a/lib/internal/Magento/Framework/Api/Config/SchemaLocator.php b/lib/internal/Magento/Framework/Api/Config/SchemaLocator.php index 86662ab2528136ecc4c8182d650a55b9e80f0a2f..1e8334c643def347b9392eba52d624b5fdaab556 100644 --- a/lib/internal/Magento/Framework/Api/Config/SchemaLocator.php +++ b/lib/internal/Magento/Framework/Api/Config/SchemaLocator.php @@ -16,7 +16,7 @@ class SchemaLocator implements \Magento\Framework\Config\SchemaLocatorInterface */ public function getSchema() { - return realpath(__DIR__ . '/../etc/data_object.xsd'); + return realpath(__DIR__ . '/../etc/service_data_attributes.xsd'); } /** diff --git a/lib/internal/Magento/Framework/Api/DataObjectHelper.php b/lib/internal/Magento/Framework/Api/DataObjectHelper.php index d0a22a194be444bbc4b71f5d60dd6bf29fd1de42..fc3a3b0920c60430f28de95db7cd6a6ab84f6868 100644 --- a/lib/internal/Magento/Framework/Api/DataObjectHelper.php +++ b/lib/internal/Magento/Framework/Api/DataObjectHelper.php @@ -6,6 +6,8 @@ namespace Magento\Framework\Api; +use Magento\Framework\Reflection\MethodsMap; + class DataObjectHelper { /** @@ -28,22 +30,30 @@ class DataObjectHelper */ protected $extensionFactory; + /** + * @var MethodsMap + */ + protected $methodsMapProcessor; + /** * @param ObjectFactory $objectFactory * @param \Magento\Framework\Reflection\DataObjectProcessor $objectProcessor * @param \Magento\Framework\Reflection\TypeProcessor $typeProcessor * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory + * @param MethodsMap $methodsMapProcessor */ public function __construct( ObjectFactory $objectFactory, \Magento\Framework\Reflection\DataObjectProcessor $objectProcessor, \Magento\Framework\Reflection\TypeProcessor $typeProcessor, - \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory + \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, + MethodsMap $methodsMapProcessor ) { $this->objectFactory = $objectFactory; $this->objectProcessor = $objectProcessor; $this->typeProcessor = $typeProcessor; $this->extensionFactory = $extensionFactory; + $this->methodsMapProcessor = $methodsMapProcessor; } /** @@ -128,7 +138,7 @@ class DataObjectHelper if ($interfaceName == null) { $interfaceName = get_class($dataObject); } - $returnType = $this->objectProcessor->getMethodReturnType($interfaceName, $getterMethodName); + $returnType = $this->methodsMapProcessor->getMethodReturnType($interfaceName, $getterMethodName); if ($this->typeProcessor->isTypeSimple($returnType)) { $dataObject->$methodName($value); return $this; diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesGeneratorTest.php b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesGeneratorTest.php index 5207a20588c73dcbf83aa767b907f585db8bb114..efdeb4ae914348fa51302891271d018b06c70067 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesGeneratorTest.php +++ b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesGeneratorTest.php @@ -6,6 +6,8 @@ // @codingStandardsIgnoreFile namespace Magento\Framework\Api\Test\Unit\Code\Generator; +use Magento\Framework\Api\Config\Converter; + class ExtensionAttributesGeneratorTest extends \PHPUnit_Framework_TestCase { /** @@ -44,11 +46,21 @@ class ExtensionAttributesGeneratorTest extends \PHPUnit_Framework_TestCase ->willReturn( [ 'Magento\Catalog\Api\Data\ProductInterface' => [ - 'string_attribute' => 'string', - 'complex_object_attribute' => '\Magento\Bundle\Api\Data\OptionInterface[]' + 'string_attribute' => [ + Converter::DATA_TYPE => 'string', + Converter::RESOURCE_PERMISSIONS => [], + + ], + 'complex_object_attribute' => [ + Converter::DATA_TYPE => '\Magento\Bundle\Api\Data\OptionInterface[]', + Converter::RESOURCE_PERMISSIONS => [], + ], ], 'Magento\Catalog\Api\Data\Product' => [ - 'should_not_include' => 'string', + 'should_not_include' => [ + Converter::DATA_TYPE => 'string', + Converter::RESOURCE_PERMISSIONS => [], + ], ], ] ); diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesInterfaceGeneratorTest.php b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesInterfaceGeneratorTest.php index 8f80baf39beeeec1955800e7ea4a51ac12d02382..33d4ecf9e7906c2abd4bfe15ef429c539405b059 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesInterfaceGeneratorTest.php +++ b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/ExtensionAttributesInterfaceGeneratorTest.php @@ -6,6 +6,8 @@ // @codingStandardsIgnoreFile namespace Magento\Framework\Api\Test\Unit\Code\Generator; +use Magento\Framework\Api\Config\Converter; + class ExtensionAttributesInterfaceGeneratorTest extends \PHPUnit_Framework_TestCase { public function testGenerate() @@ -19,11 +21,20 @@ class ExtensionAttributesInterfaceGeneratorTest extends \PHPUnit_Framework_TestC ->willReturn( [ 'Magento\Catalog\Api\Data\ProductInterface' => [ - 'string_attribute' => 'string', - 'complex_object_attribute' => '\Magento\Bundle\Api\Data\OptionInterface[]' + 'string_attribute' => [ + Converter::DATA_TYPE => 'string', + Converter::RESOURCE_PERMISSIONS => [], + ], + 'complex_object_attribute' => [ + Converter::DATA_TYPE => '\Magento\Bundle\Api\Data\OptionInterface[]', + Converter::RESOURCE_PERMISSIONS => [], + ], ], 'Magento\Catalog\Api\Data\Product' => [ - 'should_not_include' => 'string', + 'should_not_include' => [ + Converter::DATA_TYPE => 'string', + Converter::RESOURCE_PERMISSIONS => [], + ], ], ] ); diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtension.txt b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtension.txt index 8b5caad1ecc4456ae51380e25c735be8f2660d37..0f9838bd8736c9c0545b6bb82ab5adbe9762a946 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtension.txt +++ b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtension.txt @@ -6,7 +6,7 @@ namespace Magento\Catalog\Api\Data; class ProductExtension extends \Magento\Framework\Api\AbstractSimpleObject implements \Magento\Catalog\Api\Data\ProductExtensionInterface { /** - * @return string + * @return string|null */ public function getStringAttribute() { @@ -24,7 +24,7 @@ class ProductExtension extends \Magento\Framework\Api\AbstractSimpleObject imple } /** - * @return \Magento\Bundle\Api\Data\OptionInterface[] + * @return \Magento\Bundle\Api\Data\OptionInterface[]|null */ public function getComplexObjectAttribute() { diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtensionInterface.txt b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtensionInterface.txt index ec9edd7affc2d68e5444d914b833bff81ccc1d4d..75dde39b2151952b49beb587ce4eecff56418997 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtensionInterface.txt +++ b/lib/internal/Magento/Framework/Api/Test/Unit/Code/Generator/_files/SampleExtensionInterface.txt @@ -6,7 +6,7 @@ namespace Magento\Catalog\Api\Data; interface ProductExtensionInterface extends \Magento\Framework\Api\ExtensionAttributesInterface { /** - * @return string + * @return string|null */ public function getStringAttribute(); @@ -17,7 +17,7 @@ interface ProductExtensionInterface extends \Magento\Framework\Api\ExtensionAttr public function setStringAttribute($stringAttribute); /** - * @return \Magento\Bundle\Api\Data\OptionInterface[] + * @return \Magento\Bundle\Api\Data\OptionInterface[]|null */ public function getComplexObjectAttribute(); diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Config/ConverterTest.php b/lib/internal/Magento/Framework/Api/Test/Unit/Config/ConverterTest.php index 94f5a4494a75dd9c57afa409c90eb622ad26827c..d4629213b20be2d7bbca2ff16bacf0b36de608e5 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/Config/ConverterTest.php +++ b/lib/internal/Magento/Framework/Api/Test/Unit/Config/ConverterTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\Api\Test\Unit\Config; +use Magento\Framework\Api\Config\Converter; + class ConverterTest extends \PHPUnit_Framework_TestCase { /** @@ -47,15 +49,39 @@ class ConverterTest extends \PHPUnit_Framework_TestCase 'Magento\Tax\Api\Data\TaxRateInterface' => [ ], 'Magento\Catalog\Api\Data\ProductInterface' => [ - 'stock_item' => 'Magento\CatalogInventory\Api\Data\StockItemInterface' + 'stock_item' => [ + Converter::DATA_TYPE => 'Magento\CatalogInventory\Api\Data\StockItemInterface', + Converter::RESOURCE_PERMISSIONS => [], + ], ], 'Magento\Customer\Api\Data\CustomerInterface' => [ - 'custom_1' => 'Magento\Customer\Api\Data\CustomerCustom', - 'custom_2' => 'Magento\CustomerExtra\Api\Data\CustomerCustom2' + 'custom_1' => [ + Converter::DATA_TYPE => 'Magento\Customer\Api\Data\CustomerCustom', + Converter::RESOURCE_PERMISSIONS => [], + ], + 'custom_2' => [ + Converter::DATA_TYPE => 'Magento\CustomerExtra\Api\Data\CustomerCustom2', + Converter::RESOURCE_PERMISSIONS => [], + ], + ], + 'Magento\Customer\Api\Data\CustomerInterface2' => [ + 'custom_with_permission' => [ + Converter::DATA_TYPE => 'Magento\Customer\Api\Data\CustomerCustom', + Converter::RESOURCE_PERMISSIONS => [ + 'Magento_Customer::manage', + ], + ], + 'custom_with_multiple_permissions' => [ + Converter::DATA_TYPE => 'Magento\CustomerExtra\Api\Data\CustomerCustom2', + Converter::RESOURCE_PERMISSIONS => [ + 'Magento_Customer::manage', + 'Magento_Customer::manage2', + ], + ], ], ]; - $xmlFile = __DIR__ . '/_files/data_object_valid.xml'; + $xmlFile = __DIR__ . '/_files/service_data_attributes.xml'; $dom = new \DOMDocument(); $dom->loadXML(file_get_contents($xmlFile)); $result = $this->_converter->convert($dom); diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Config/SchemaLocatorTest.php b/lib/internal/Magento/Framework/Api/Test/Unit/Config/SchemaLocatorTest.php index f0df38d59a070958d77d6ddccb0d339162d256da..f05583db0490194e949261bce0a2c176932dbdb5 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/Config/SchemaLocatorTest.php +++ b/lib/internal/Magento/Framework/Api/Test/Unit/Config/SchemaLocatorTest.php @@ -22,7 +22,7 @@ class SchemaLocatorTest extends \PHPUnit_Framework_TestCase public function testGetSchema() { - $expected = str_replace('\\', '/', BP . '/lib/internal/Magento/Framework/Api/etc/data_object.xsd'); + $expected = str_replace('\\', '/', BP . '/lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd'); $actual = str_replace('\\', '/', $this->_model->getSchema()); $this->assertEquals($expected, $actual); } @@ -30,7 +30,7 @@ class SchemaLocatorTest extends \PHPUnit_Framework_TestCase public function testGetPerFileSchema() { $actual = str_replace('\\', '/', $this->_model->getPerFileSchema()); - $expected = str_replace('\\', '/', BP . '/lib/internal/Magento/Framework/Api/etc/data_object.xsd'); + $expected = str_replace('\\', '/', BP . '/lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd'); $this->assertEquals($expected, $actual); } } diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Config/_files/data_object_valid.xml b/lib/internal/Magento/Framework/Api/Test/Unit/Config/_files/data_object_valid.xml deleted file mode 100644 index 67606deab2eb46e78dc3022a723b41a1bb25bfa3..0000000000000000000000000000000000000000 --- a/lib/internal/Magento/Framework/Api/Test/Unit/Config/_files/data_object_valid.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> - <custom_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> - </custom_attributes> - <custom_attributes for="Magento\Catalog\Api\Data\ProductInterface"> - <attribute code="stock_item" type="Magento\CatalogInventory\Api\Data\StockItemInterface" /> - </custom_attributes> - <custom_attributes for="Magento\Customer\Api\Data\CustomerInterface"> - <attribute code="custom_1" type="Magento\Customer\Api\Data\CustomerCustom" /> - <attribute code="custom_2" type="Magento\CustomerExtra\Api\Data\CustomerCustom2" /> - </custom_attributes> -</config> diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/Config/_files/service_data_attributes.xml b/lib/internal/Magento/Framework/Api/Test/Unit/Config/_files/service_data_attributes.xml new file mode 100644 index 0000000000000000000000000000000000000000..4f6a9838bfcdb6bd9dbfa20b18ffcfde80bd625d --- /dev/null +++ b/lib/internal/Magento/Framework/Api/Test/Unit/Config/_files/service_data_attributes.xml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd"> + <extension_attributes for="Magento\Tax\Api\Data\TaxRateInterface"> + </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> + <attribute code="stock_item" type="Magento\CatalogInventory\Api\Data\StockItemInterface" /> + </extension_attributes> + <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface"> + <attribute code="custom_1" type="Magento\Customer\Api\Data\CustomerCustom" /> + <attribute code="custom_2" type="Magento\CustomerExtra\Api\Data\CustomerCustom2" /> + </extension_attributes> + <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface2"> + <attribute code="custom_with_permission" type="Magento\Customer\Api\Data\CustomerCustom"> + <resources> + <resource ref="Magento_Customer::manage"/> + </resources> + </attribute> + <attribute code="custom_with_multiple_permissions" type="Magento\CustomerExtra\Api\Data\CustomerCustom2"> + <resources> + <resource ref="Magento_Customer::manage"/> + <resource ref="Magento_Customer::manage2"/> + </resources> + </attribute> + </extension_attributes> +</config> diff --git a/lib/internal/Magento/Framework/Api/Test/Unit/DataObjectHelperTest.php b/lib/internal/Magento/Framework/Api/Test/Unit/DataObjectHelperTest.php index ff229abd7d22e9be0f810da4034336e5e9631f63..65c7cb906d1f2f15b8f251145633cdb6e85848c5 100644 --- a/lib/internal/Magento/Framework/Api/Test/Unit/DataObjectHelperTest.php +++ b/lib/internal/Magento/Framework/Api/Test/Unit/DataObjectHelperTest.php @@ -43,6 +43,11 @@ class DataObjectHelperTest extends \PHPUnit_Framework_TestCase */ protected $attributeValueFactoryMock; + /** + * @var \Magento\Framework\Reflection\MethodsMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected $methodsMapProcessor; + public function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -53,6 +58,9 @@ class DataObjectHelperTest extends \PHPUnit_Framework_TestCase $this->objectProcessorMock = $this->getMockBuilder('\Magento\Framework\Reflection\DataObjectProcessor') ->disableOriginalConstructor() ->getMock(); + $this->methodsMapProcessor = $this->getMockBuilder('\Magento\Framework\Reflection\MethodsMap') + ->disableOriginalConstructor() + ->getMock(); $this->attributeValueFactoryMock = $this->getMockBuilder('\Magento\Framework\Api\AttributeValueFactory') ->disableOriginalConstructor() ->getMock(); @@ -63,6 +71,7 @@ class DataObjectHelperTest extends \PHPUnit_Framework_TestCase 'objectFactory' => $this->objectFactoryMock, 'typeProcessor' => $this->typeProcessor, 'objectProcessor' => $this->objectProcessorMock, + 'methodsMapProcessor' => $this->methodsMapProcessor, ] ); } @@ -103,11 +112,11 @@ class DataObjectHelperTest extends \PHPUnit_Framework_TestCase ], ]; - $this->objectProcessorMock->expects($this->at(0)) + $this->methodsMapProcessor->expects($this->at(0)) ->method('getMethodReturnType') ->with('\Magento\Customer\Api\Data\AddressInterface', 'getStreet') ->willReturn('string[]'); - $this->objectProcessorMock->expects($this->at(1)) + $this->methodsMapProcessor->expects($this->at(1)) ->method('getMethodReturnType') ->with('\Magento\Customer\Api\Data\AddressInterface', 'getRegion') ->willReturn('\Magento\Customer\Api\Data\RegionInterface'); @@ -317,11 +326,11 @@ class DataObjectHelperTest extends \PHPUnit_Framework_TestCase ->method('buildOutputDataArray') ->with($secondAddressDataObject, get_class($firstAddressDataObject)) ->willReturn($data2); - $this->objectProcessorMock->expects($this->at(1)) + $this->methodsMapProcessor->expects($this->at(0)) ->method('getMethodReturnType') ->with('Magento\Customer\Model\Data\Address', 'getStreet') ->willReturn('string[]'); - $this->objectProcessorMock->expects($this->at(2)) + $this->methodsMapProcessor->expects($this->at(1)) ->method('getMethodReturnType') ->with('Magento\Customer\Model\Data\Address', 'getRegion') ->willReturn('\Magento\Customer\Api\Data\RegionInterface'); diff --git a/lib/internal/Magento/Framework/Api/etc/data_object.xsd b/lib/internal/Magento/Framework/Api/etc/data_object.xsd deleted file mode 100644 index 0983dbbf912385c5a83dd1b8db18d768071c42fc..0000000000000000000000000000000000000000 --- a/lib/internal/Magento/Framework/Api/etc/data_object.xsd +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:element name="config"> - <xs:complexType> - <xs:sequence> - <xs:element name="custom_attributes" type="custom_attributesType" minOccurs="1" maxOccurs="unbounded"> - <xs:annotation> - <xs:documentation>Main schema element. Extended Attributes</xs:documentation> - </xs:annotation> - </xs:element> - </xs:sequence> - </xs:complexType> - </xs:element> - <xs:complexType name="attributeType"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute type="xs:string" name="code" use="required"/> - <xs:attribute type="xs:string" name="type" use="required"/> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - <xs:complexType name="custom_attributesType"> - <xs:sequence> - <xs:element type="attributeType" name="attribute" minOccurs="0" maxOccurs="unbounded"/> - </xs:sequence> - <xs:attribute type="xs:string" name="for" use="required"/> - </xs:complexType> -</xs:schema> diff --git a/lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd b/lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd new file mode 100644 index 0000000000000000000000000000000000000000..24590e3d758c79afeef7ce85c8cb9f6547e6f524 --- /dev/null +++ b/lib/internal/Magento/Framework/Api/etc/service_data_attributes.xsd @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="config"> + <xs:complexType> + <xs:sequence> + <xs:element name="extension_attributes" type="extension_attributesType" minOccurs="1" + maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>Main schema element. Extended Attributes</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:complexType name="attributeType"> + <xs:sequence> + <xs:element name="resources" type="resourcesType" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute type="xs:string" name="code" use="required"/> + <xs:attribute type="xs:string" name="type" use="required"/> + </xs:complexType> + <xs:complexType name="extension_attributesType"> + <xs:sequence> + <xs:element type="attributeType" name="attribute" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute type="xs:string" name="for" use="required"/> + </xs:complexType> + <xs:complexType name="resourcesType"> + <xs:sequence> + <xs:element name="resource" type="resourceType" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="resourceType"> + <xs:attribute name="ref" use="required"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value=".+(, ?.+)*"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> +</xs:schema> diff --git a/lib/internal/Magento/Framework/Reflection/CustomAttributesProcessor.php b/lib/internal/Magento/Framework/Reflection/CustomAttributesProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..0a98c9e39e8c2f135b962bfbf0ef51d44924c183 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/CustomAttributesProcessor.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Reflection; + +use Magento\Framework\Phrase; +use Magento\Framework\Api\AttributeInterface; +use Magento\Framework\Api\AttributeValue; +use Magento\Framework\Api\SimpleDataObjectConverter; +use Zend\Code\Reflection\MethodReflection; +use Magento\Framework\Api\CustomAttributesDataInterface; +use Magento\Framework\Api\AttributeTypeResolverInterface; + +/** + * Processes custom attributes and produces an array for the data. + */ +class CustomAttributesProcessor +{ + /** + * @var DataObjectProcessor + */ + private $dataObjectProcessor; + + /** + * @var AttributeTypeResolverInterface + */ + private $attributeTypeResolver; + + /** + * @param DataObjectProcessor $dataObjectProcessor + * @param AttributeTypeResolverInterface $typeResolver + */ + public function __construct( + DataObjectProcessor $dataObjectProcessor, + AttributeTypeResolverInterface $typeResolver + ) { + $this->dataObjectProcessor = $dataObjectProcessor; + $this->attributeTypeResolver = $typeResolver; + } + + /** + * Writes out the custom attributes for a given object into an array. + * + * @param CustomAttributesDataInterface $objectWithCustomAttributes + * @param string $dataObjectType + * @return array + */ + public function buildOutputDataArray(CustomAttributesDataInterface $objectWithCustomAttributes, $dataObjectType) + { + $customAttributes = $objectWithCustomAttributes->getCustomAttributes(); + $result = []; + foreach ($customAttributes as $customAttribute) { + $result[] = $this->convertCustomAttribute($customAttribute, $dataObjectType); + } + return $result; + } + + /** + * Convert custom_attribute object to use flat array structure + * + * @param AttributeInterface $customAttribute + * @param string $dataObjectType + * @return array + */ + private function convertCustomAttribute(AttributeInterface $customAttribute, $dataObjectType) + { + $data = []; + $data[AttributeValue::ATTRIBUTE_CODE] = $customAttribute->getAttributeCode(); + $value = $customAttribute->getValue(); + if (is_object($value)) { + $type = $this->attributeTypeResolver->resolveObjectType( + $customAttribute->getAttributeCode(), + $value, + $dataObjectType + ); + $value = $this->dataObjectProcessor->buildOutputDataArray($value, $type); + } elseif (is_array($value)) { + $valueResult = []; + foreach ($value as $singleValue) { + if (is_object($singleValue)) { + $type = $this->attributeTypeResolver->resolveObjectType( + $customAttribute->getAttributeCode(), + $singleValue, + $dataObjectType + ); + $singleValue = $this->dataObjectProcessor->buildOutputDataArray($singleValue, $type); + } + // Cannot cast to a type because the type is unknown + $valueResult[] = $singleValue; + } + $value = $valueResult; + } + $data[AttributeValue::VALUE] = $value; + return $data; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php b/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php index 2b9977d7355f565e808b15cc539c7c892f38afa6..4adceb83c7720be26a310fe8d7dea3f19726219c 100644 --- a/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php @@ -14,54 +14,54 @@ use Zend\Code\Reflection\ClassReflection; use Zend\Code\Reflection\MethodReflection; /** - * Data object processor for de-serialization using class reflection + * Data object processor for array serialization using class reflection */ class DataObjectProcessor { - const IS_METHOD_PREFIX = 'is'; - const HAS_METHOD_PREFIX = 'has'; - const GETTER_PREFIX = 'get'; - const SERVICE_INTERFACE_METHODS_CACHE_PREFIX = 'serviceInterfaceMethodsMap'; - const BASE_MODEL_CLASS = 'Magento\Framework\Model\AbstractExtensibleModel'; - /** - * @var \Magento\Framework\Cache\FrontendInterface + * @var MethodsMap */ - protected $cache; + private $methodsMapProcessor; /** - * @var TypeProcessor + * @var TypeCaster */ - protected $typeProcessor; + private $typeCaster; /** - * @var array + * @var FieldNamer */ - protected $dataInterfaceMethodsMap = []; + private $fieldNamer; /** - * @var array + * @var ExtensionAttributesProcessor */ - protected $serviceInterfaceMethodsMap = []; + private $extensionAttributesProcessor; /** - * @var \Magento\Framework\Api\AttributeTypeResolverInterface + * @var CustomAttributesProcessor */ - protected $attributeTypeResolver; + private $customAttributesProcessor; /** - * @param \Magento\Framework\Cache\FrontendInterface $cache - * @param TypeProcessor $typeProcessor - * @param \Magento\Framework\Api\AttributeTypeResolverInterface $typeResolver + * @param MethodsMap $methodsMapProcessor + * @param TypeCaster $typeCaster + * @param FieldNamer $fieldNamer + * @param CustomAttributesProcessor $customAttributesProcessor + * @param ExtensionAttributesProcessor $extensionAttributesProcessor */ public function __construct( - \Magento\Framework\Cache\FrontendInterface $cache, - TypeProcessor $typeProcessor, - \Magento\Framework\Api\AttributeTypeResolverInterface $typeResolver + MethodsMap $methodsMapProcessor, + TypeCaster $typeCaster, + FieldNamer $fieldNamer, + CustomAttributesProcessor $customAttributesProcessor, + ExtensionAttributesProcessor $extensionAttributesProcessor ) { - $this->cache = $cache; - $this->typeProcessor = $typeProcessor; - $this->attributeTypeResolver = $typeResolver; + $this->methodsMapProcessor = $methodsMapProcessor; + $this->typeCaster = $typeCaster; + $this->fieldNamer = $fieldNamer; + $this->extensionAttributesProcessor = $extensionAttributesProcessor; + $this->customAttributesProcessor = $customAttributesProcessor; } /** @@ -74,43 +74,36 @@ class DataObjectProcessor */ public function buildOutputDataArray($dataObject, $dataObjectType) { - $methods = $this->getMethodsMap($dataObjectType); + $methods = $this->methodsMapProcessor->getMethodsMap($dataObjectType); $outputData = []; /** @var MethodReflection $method */ - foreach ($methods as $methodName => $methodReflectionData) { - // any method with parameter(s) gets ignored because we do not know the type and value of - // the parameter(s), so we are not able to process - if ($methodReflectionData['parameterCount'] > 0) { + foreach (array_keys($methods) as $methodName) { + if (!$this->methodsMapProcessor->isMethodValidForDataField($dataObjectType, $methodName)) { continue; } - $returnType = $methodReflectionData['type']; - if (substr($methodName, 0, 2) === self::IS_METHOD_PREFIX) { - $value = $dataObject->{$methodName}(); - if ($value === null && !$methodReflectionData['isRequired']) { - continue; - } - $key = SimpleDataObjectConverter::camelCaseToSnakeCase(substr($methodName, 2)); - $outputData[$key] = $this->castValueToType($value, $returnType); - } elseif (substr($methodName, 0, 3) === self::HAS_METHOD_PREFIX) { - $value = $dataObject->{$methodName}(); - if ($value === null && !$methodReflectionData['isRequired']) { - continue; - } - $key = SimpleDataObjectConverter::camelCaseToSnakeCase(substr($methodName, 3)); - $outputData[$key] = $this->castValueToType($value, $returnType); - } elseif (substr($methodName, 0, 3) === self::GETTER_PREFIX) { - $value = $dataObject->{$methodName}(); - if ($methodName === 'getCustomAttributes' && $value === []) { - continue; - } - if ($value === null && !$methodReflectionData['isRequired']) { - continue; - } - $key = SimpleDataObjectConverter::camelCaseToSnakeCase(substr($methodName, 3)); - if ($key === CustomAttributesDataInterface::CUSTOM_ATTRIBUTES) { - $value = $this->convertCustomAttributes($value, $dataObjectType); - } elseif (is_object($value) && !($value instanceof Phrase)) { + + $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 = []; @@ -119,192 +112,16 @@ class DataObjectProcessor if (is_object($singleValue) && !($singleValue instanceof Phrase)) { $singleValue = $this->buildOutputDataArray($singleValue, $arrayElementType); } - $valueResult[] = $this->castValueToType($singleValue, $arrayElementType); + $valueResult[] = $this->typeCaster->castValueToType($singleValue, $arrayElementType); } $value = $valueResult; + } else { + $value = $this->typeCaster->castValueToType($value, $returnType); } - $outputData[$key] = $this->castValueToType($value, $returnType); } - } - return $outputData; - } - /** - * Cast the output type to the documented type. This helps for output purposes. - * - * @param mixed $value - * @param string $type - * @return mixed - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function castValueToType($value, $type) - { - if ($value === null) { - return null; + $outputData[$key] = $value; } - - if ($type === "int" || $type === "integer") { - return (int)$value; - } - - if ($type === "string") { - return (string)$value; - } - - if ($type === "bool" || $type === "boolean" || $type === "true" || $type == "false") { - return (bool)$value; - } - - if ($type === "float") { - return (float)$value; - } - - if ($type === "double") { - return (double)$value; - } - - return $value; - } - - /** - * Get return type by interface name and method - * - * @param string $interfaceName - * @param string $methodName - * @return string - */ - public function getMethodReturnType($interfaceName, $methodName) - { - return $this->getMethodsMap($interfaceName)[$methodName]['type']; - } - - /** - * Convert array of custom_attributes to use flat array structure - * - * @param \Magento\Framework\Api\AttributeInterface[] $customAttributes - * @param string $dataObjectType - * @return array - */ - protected function convertCustomAttributes($customAttributes, $dataObjectType) - { - $result = []; - foreach ((array)$customAttributes as $customAttribute) { - $result[] = $this->convertCustomAttribute($customAttribute, $dataObjectType); - } - return $result; - } - - /** - * Convert custom_attribute object to use flat array structure - * - * @param \Magento\Framework\Api\AttributeInterface $customAttribute - * @param string $dataObjectType - * @return array - */ - protected function convertCustomAttribute($customAttribute, $dataObjectType) - { - $data = []; - $data[AttributeValue::ATTRIBUTE_CODE] = $customAttribute->getAttributeCode(); - $value = $customAttribute->getValue(); - if (is_object($value)) { - $type = $this->attributeTypeResolver->resolveObjectType( - $customAttribute->getAttributeCode(), - $value, - $dataObjectType - ); - $value = $this->buildOutputDataArray($value, $type); - } elseif (is_array($value)) { - $valueResult = []; - foreach ($value as $singleValue) { - if (is_object($singleValue)) { - $type = $this->attributeTypeResolver->resolveObjectType( - $customAttribute->getAttributeCode(), - $singleValue, - $dataObjectType - ); - $singleValue = $this->buildOutputDataArray($singleValue, $type); - } - // Cannot cast to a type because the type is unknown - $valueResult[] = $singleValue; - } - $value = $valueResult; - } - $data[AttributeValue::VALUE] = $value; - return $data; - } - - /** - * Return service interface or Data interface methods loaded from cache - * - * @param string $interfaceName - * @return array - * <pre> - * Service methods' reflection data stored in cache as 'methodName' => 'returnType' - * ex. - * [ - * 'create' => '\Magento\Customer\Api\Data\Customer', - * 'validatePassword' => 'boolean' - * ] - * </pre> - */ - public function getMethodsMap($interfaceName) - { - $key = self::SERVICE_INTERFACE_METHODS_CACHE_PREFIX . "-" . md5($interfaceName); - if (!isset($this->serviceInterfaceMethodsMap[$key])) { - $methodMap = $this->cache->load($key); - if ($methodMap) { - $this->serviceInterfaceMethodsMap[$key] = unserialize($methodMap); - } else { - $methodMap = $this->getMethodMapViaReflection($interfaceName); - $this->serviceInterfaceMethodsMap[$key] = $methodMap; - $this->cache->save(serialize($this->serviceInterfaceMethodsMap[$key]), $key); - } - } - return $this->serviceInterfaceMethodsMap[$key]; - } - - /** - * Use reflection to load the method information - * - * @param string $interfaceName - * @return array - */ - protected function getMethodMapViaReflection($interfaceName) - { - $methodMap = []; - $class = new ClassReflection($interfaceName); - $baseClassMethods = false; - foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { - // Include all the methods of classes inheriting from AbstractExtensibleObject. - // Ignore all the methods of AbstractExtensibleModel's parent classes - if ($method->class === self::BASE_MODEL_CLASS) { - $baseClassMethods = true; - } elseif ($baseClassMethods) { - // ReflectionClass::getMethods() sorts the methods by class (lowest in inheritance tree first) - // then by the order they are defined in the class definition - break; - } - - if ($this->isSuitableMethod($method)) { - $methodMap[$method->getName()] = $this->typeProcessor->getGetterReturnType($method); - } - } - return $methodMap; - } - - /** - * Determines if the method is suitable to be used by the processor. - * - * @param \ReflectionMethod $method - * @return bool - */ - protected function isSuitableMethod($method) - { - $isSuitableMethodType = !($method->isConstructor() || $method->isFinal() - || $method->isStatic() || $method->isDestructor()); - - $isExcludedMagicMethod = strpos($method->getName(), '__') === 0; - return $isSuitableMethodType && !$isExcludedMagicMethod; + return $outputData; } } diff --git a/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..c2d2d69c758f8b4676da91027080fb5736d5c880 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php @@ -0,0 +1,189 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Reflection; + +use Magento\Framework\Api\Config\Reader as ExtensionAttributesConfigReader; +use Magento\Framework\Api\Config\Converter; +use Magento\Framework\AuthorizationInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Api\SimpleDataObjectConverter; +use Magento\Framework\Api\ExtensionAttributesInterface; +use Magento\Framework\Reflection\MethodsMap; +use Zend\Code\Reflection\MethodReflection; + +/** + * Processes extension attributes and produces an array for the data. + */ +class ExtensionAttributesProcessor +{ + /** + * @var DataObjectProcessor + */ + private $dataObjectProcessor; + + /** + * @var MethodsMap + */ + private $methodsMapProcessor; + + /** + * @var AuthorizationInterface + */ + private $authorization; + + /** + * @var ExtensionAttributesConfigReader + */ + private $configReader; + + /** + * @var bool + */ + private $isPermissionChecked; + + /** + * @var FieldNamer + */ + private $fieldNamer; + + /** + * @var TypeCaster + */ + private $typeCaster; + + /** + * @param DataObjectProcessor $dataObjectProcessor + * @param MethodsMap $methodsMapProcessor + * @param TypeCaster $typeCaster + * @param FieldNamer $fieldNamer + * @param AuthorizationInterface $authorization + * @param ExtensionAttributesConfigReader $configReader + * @param bool $isPermissionChecked + */ + public function __construct( + DataObjectProcessor $dataObjectProcessor, + MethodsMap $methodsMapProcessor, + TypeCaster $typeCaster, + FieldNamer $fieldNamer, + AuthorizationInterface $authorization, + ExtensionAttributesConfigReader $configReader, + $isPermissionChecked = false + ) { + $this->dataObjectProcessor = $dataObjectProcessor; + $this->methodsMapProcessor = $methodsMapProcessor; + $this->typeCaster = $typeCaster; + $this->fieldNamer = $fieldNamer; + $this->authorization = $authorization; + $this->configReader = $configReader; + $this->isPermissionChecked = $isPermissionChecked; + } + + /** + * 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; + } + + /** + * @param string $dataObjectType + * @param string $attributeCode + * @return bool + */ + private function isAttributePermissionValid($dataObjectType, $attributeCode) + { + $typeName = $this->getRegularTypeForExtensionAttributesType($dataObjectType); + $permissions = $this->getPermissionsForTypeAndMethod($typeName, $attributeCode); + foreach ($permissions as $permission) { + if (!$this->authorization->isAllowed($permission)) { + return false; + } + } + + return true; + } + + /** + * @param string $name + * @return string + */ + private function getRegularTypeForExtensionAttributesType($name) + { + return ltrim(str_replace('ExtensionInterface', 'Interface', $name), '\\'); + } + + /** + * @param string $typeName + * @param string $attributeCode + * @return string[] A list of permissions + */ + private function getPermissionsForTypeAndMethod($typeName, $attributeCode) + { + // TODO: Move function to the Config and hope this is cached + $attributes = $this->configReader->read(); + if (isset($attributes[$typeName]) && isset($attributes[$typeName][$attributeCode])) { + $attributeMetadata = $attributes[$typeName][$attributeCode]; + $permissions = []; + foreach ($attributeMetadata[Converter::RESOURCE_PERMISSIONS] as $permission) { + $permissions[] = $permission; + } + return $permissions; + } + + return []; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/FieldNamer.php b/lib/internal/Magento/Framework/Reflection/FieldNamer.php new file mode 100644 index 0000000000000000000000000000000000000000..2e105b9a4f666e2073124e8817a9208762740389 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/FieldNamer.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Reflection; + +use Magento\Framework\Phrase; +use Magento\Framework\Api\AttributeValue; +use Magento\Framework\Api\CustomAttributesDataInterface; +use Magento\Framework\Api\SimpleDataObjectConverter; +use Zend\Code\Reflection\ClassReflection; +use Zend\Code\Reflection\MethodReflection; + +/** + * Determines the name to use for fields in a data output array given method metadata. + */ +class FieldNamer +{ + const IS_METHOD_PREFIX = 'is'; + const HAS_METHOD_PREFIX = 'has'; + const GETTER_PREFIX = 'get'; + + /** + * Converts a method's name into a data field name. + * + * @param string $methodName + * @return string|null + */ + public function getFieldNameForMethodName($methodName) + { + if (substr($methodName, 0, 2) === self::IS_METHOD_PREFIX) { + return SimpleDataObjectConverter::camelCaseToSnakeCase(substr($methodName, 2)); + } elseif (substr($methodName, 0, 3) === self::HAS_METHOD_PREFIX) { + return SimpleDataObjectConverter::camelCaseToSnakeCase(substr($methodName, 3)); + } elseif (substr($methodName, 0, 3) === self::GETTER_PREFIX) { + return SimpleDataObjectConverter::camelCaseToSnakeCase(substr($methodName, 3)); + } + + return null; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/MethodsMap.php b/lib/internal/Magento/Framework/Reflection/MethodsMap.php new file mode 100644 index 0000000000000000000000000000000000000000..36b446d1b870a32d6f468bf77b293a63b024fabe --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/MethodsMap.php @@ -0,0 +1,180 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Reflection; + +use Zend\Code\Reflection\ClassReflection; +use Zend\Code\Reflection\MethodReflection; + +/** + * Gathers method metadata information. + */ +class MethodsMap +{ + const SERVICE_INTERFACE_METHODS_CACHE_PREFIX = 'serviceInterfaceMethodsMap'; + const BASE_MODEL_CLASS = 'Magento\Framework\Model\AbstractExtensibleModel'; + + /** + * @var \Magento\Framework\Cache\FrontendInterface + */ + private $cache; + + /** + * @var TypeProcessor + */ + private $typeProcessor; + + /** + * @var array + */ + private $serviceInterfaceMethodsMap = []; + + /** + * @var FieldNamer + */ + private $fieldNamer; + + /** + * @param \Magento\Framework\Cache\FrontendInterface $cache + * @param TypeProcessor $typeProcessor + * @param \Magento\Framework\Api\AttributeTypeResolverInterface $typeResolver + * @param FieldNamer $fieldNamer + */ + public function __construct( + \Magento\Framework\Cache\FrontendInterface $cache, + TypeProcessor $typeProcessor, + \Magento\Framework\Api\AttributeTypeResolverInterface $typeResolver, + FieldNamer $fieldNamer + ) { + $this->cache = $cache; + $this->typeProcessor = $typeProcessor; + $this->attributeTypeResolver = $typeResolver; + $this->fieldNamer = $fieldNamer; + } + + /** + * Get return type by type name and method name. + * + * @param string $typeName + * @param string $methodName + * @return string + */ + public function getMethodReturnType($typeName, $methodName) + { + return $this->getMethodsMap($typeName)[$methodName]['type']; + } + + /** + * Return service interface or Data interface methods loaded from cache + * + * @param string $interfaceName + * @return array + * <pre> + * Service methods' reflection data stored in cache as 'methodName' => 'returnType' + * ex. + * [ + * 'create' => '\Magento\Customer\Api\Data\Customer', + * 'validatePassword' => 'boolean' + * ] + * </pre> + */ + public function getMethodsMap($interfaceName) + { + $key = self::SERVICE_INTERFACE_METHODS_CACHE_PREFIX . "-" . md5($interfaceName); + if (!isset($this->serviceInterfaceMethodsMap[$key])) { + $methodMap = $this->cache->load($key); + if ($methodMap) { + $this->serviceInterfaceMethodsMap[$key] = unserialize($methodMap); + } else { + $methodMap = $this->getMethodMapViaReflection($interfaceName); + $this->serviceInterfaceMethodsMap[$key] = $methodMap; + $this->cache->save(serialize($this->serviceInterfaceMethodsMap[$key]), $key); + } + } + return $this->serviceInterfaceMethodsMap[$key]; + } + + /** + * Use reflection to load the method information + * + * @param string $interfaceName + * @return array + */ + private function getMethodMapViaReflection($interfaceName) + { + $methodMap = []; + $class = new ClassReflection($interfaceName); + $baseClassMethods = false; + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + // Include all the methods of classes inheriting from AbstractExtensibleObject. + // Ignore all the methods of AbstractExtensibleModel's parent classes + if ($method->class === self::BASE_MODEL_CLASS) { + $baseClassMethods = true; + } elseif ($baseClassMethods) { + // ReflectionClass::getMethods() sorts the methods by class (lowest in inheritance tree first) + // then by the order they are defined in the class definition + break; + } + + if ($this->isSuitableMethod($method)) { + $methodMap[$method->getName()] = $this->typeProcessor->getGetterReturnType($method); + } + } + return $methodMap; + } + + /** + * Determines if the method is suitable to be used by the processor. + * + * @param \ReflectionMethod $method + * @return bool + */ + private function isSuitableMethod($method) + { + $isSuitableMethodType = !($method->isConstructor() || $method->isFinal() + || $method->isStatic() || $method->isDestructor()); + + $isExcludedMagicMethod = strpos($method->getName(), '__') === 0; + return $isSuitableMethodType && !$isExcludedMagicMethod; + } + + /** + * Determines if the given method's on the given type is suitable for an output data array. + * + * @param string $type + * @param string $methodName + * @return bool + */ + public function isMethodValidForDataField($type, $methodName) + { + $methods = $this->getMethodsMap($type); + if (isset($methods[$methodName])) { + $methodMetadata = $methods[$methodName]; + // any method with parameter(s) gets ignored because we do not know the type and value of + // the parameter(s), so we are not able to process + if ($methodMetadata['parameterCount'] > 0) { + return false; + } + + return $this->fieldNamer->getFieldNameForMethodName($methodName) !== null; + } + + return false; + } + + /** + * If the method has only non-null return types + * + * @param string $type + * @param string $methodName + * @return bool + */ + public function isMethodReturnValueRequired($type, $methodName) + { + $methods = $this->getMethodsMap($type); + return $methods[$methodName]['isRequired']; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/ExtensionAttributesObject.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/ExtensionAttributesObject.php new file mode 100644 index 0000000000000000000000000000000000000000..66077712a43b337238402748fe9ccf9c4b0db5f2 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/ExtensionAttributesObject.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Reflection\Test\Unit; + +use Magento\Framework\Api\ExtensionAttributesInterface; + +/** + * Dummy data object to be used by ExtensionAttributesProcessorTest + */ +class ExtensionAttributesObject implements ExtensionAttributesInterface +{ + /** + * @return string + */ + public function getAttrName() + { + return 'attrName'; + } + + /** + * @return bool + */ + public function isActive() + { + return false; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/ExtensionAttributesProcessorTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/ExtensionAttributesProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8da0c1f734f710da1c69d28c9e9f4ac02999db86 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/ExtensionAttributesProcessorTest.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Reflection\Test\Unit; + +use Magento\Framework\Api\Config\Converter; +use Magento\Framework\Api\Config\Reader; +use Magento\Framework\AuthorizationInterface; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\Reflection\ExtensionAttributesProcessor; +use Magento\Framework\Reflection\FieldNamer; +use Magento\Framework\Reflection\MethodsMap; +use Magento\Framework\Reflection\TypeCaster; + +/** + * ExtensionAttributesProcessor test + */ +class ExtensionAttributesProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ExtensionAttributesProcessor + */ + private $model; + + /** + * @var DataObjectProcessor + */ + private $dataObjectProcessorMock; + + /** + * @var MethodsMap + */ + private $methodsMapProcessorMock; + + /** + * @var FieldNamer + */ + private $fieldNamerMock; + + /** + * @var TypeCaster + */ + private $typeCasterMock; + + /** + * @var Reader + */ + private $configReaderMock; + + /** + * @var AuthorizationInterface + */ + private $authorizationMock; + /** + * Set up helper. + */ + public function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->dataObjectProcessorMock = $this->getMockBuilder('Magento\Framework\Reflection\DataObjectProcessor') + ->disableOriginalConstructor() + ->getMock(); + $this->methodsMapProcessorMock = $this->getMockBuilder('Magento\Framework\Reflection\MethodsMap') + ->disableOriginalConstructor() + ->getMock(); + $this->typeCasterMock = $this->getMockBuilder('Magento\Framework\Reflection\TypeCaster') + ->disableOriginalConstructor() + ->getMock(); + $this->fieldNamerMock = $this->getMockBuilder('Magento\Framework\Reflection\FieldNamer') + ->disableOriginalConstructor() + ->getMock(); + $this->configReaderMock = $this->getMockBuilder('Magento\Framework\Api\Config\Reader') + ->disableOriginalConstructor() + ->getMock(); + $this->authorizationMock = $this->getMockBuilder('Magento\Framework\AuthorizationInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->model = $objectManager->getObject( + 'Magento\Framework\Reflection\ExtensionAttributesProcessor', + [ + 'dataObjectProcessor' => $this->dataObjectProcessorMock, + 'methodsMapProcessor' => $this->methodsMapProcessorMock, + 'typeCaster' => $this->typeCasterMock, + 'fieldNamer' => $this->fieldNamerMock, + 'authorization' => $this->authorizationMock, + 'configReader' => $this->configReaderMock, + 'isPermissionChecked' => true, + ] + ); + } + + /** + * @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 + ); + } + + public function buildOutputDataArrayWithPermissionProvider() + { + return [ + 'permission allowed' => [ + true, + [ + 'attr_name' => 'attrName', + ], + ], + 'permission not allowed' => [ + false, + [], + ], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/FieldNamerTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/FieldNamerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1d498a940ec1a437ccc546e3629df37767748eeb --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/FieldNamerTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Reflection\Test\Unit; + +use Magento\Framework\Reflection\FieldNamer; + +/** + * Field namer Test + */ +class FieldNamerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var FieldNamer + */ + private $model; + + /** + * Set up helper. + */ + public function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject('Magento\Framework\Reflection\FieldNamer'); + } + + /** + * @param string $methodName + * @param string $expectedName + * @dataProvider methodNameProvider + */ + public function testGetFieldNameForMethodName($methodName, $expectedName) + { + $value = $this->model->getFieldNameForMethodName($methodName); + $this->assertEquals($value, $expectedName); + } + + /** + * @return array + */ + public function methodNameProvider() + { + return [ + 'isMethod' => ['isValid', 'valid'], + 'getMethod' => ['getValue', 'value'], + 'hasMethod' => ['hasStuff', 'stuff'], + 'randomMethod' => ['randomMethod', null], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/MethodsMapTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/MethodsMapTest.php new file mode 100644 index 0000000000000000000000000000000000000000..82f859897da9bfdcdd8f26de0ef7d3201712f699 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/MethodsMapTest.php @@ -0,0 +1,174 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Reflection\Test\Unit; + +use Magento\Framework\Reflection\MethodsMap; +use Magento\Framework\Reflection\TypeProcessor; +use Magento\Framework\Reflection\FieldNamer; + +/** + * MethodsMap test + */ +class MethodsMapTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MethodsMap + */ + private $model; + + /** + * Set up helper. + */ + public function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $cacheMock = $this->getMockBuilder('Magento\Framework\Cache\FrontendInterface') + ->getMockForAbstractClass(); + $cacheMock->expects($this->any()) + ->method('save'); + $cacheMock->expects($this->any()) + ->method('load') + ->will($this->returnValue(null)); + + $attributeTypeResolverMock = $this->getMockBuilder('Magento\Framework\Api\AttributeTypeResolverInterface') + ->getMockForAbstractClass(); + $fieldNamerMock = $this->getMockBuilder('Magento\Framework\Reflection\FieldNamer') + ->getMockForAbstractClass(); + $this->model = $objectManager->getObject( + 'Magento\Framework\Reflection\MethodsMap', + [ + 'cache' => $cacheMock, + 'typeProcessor' => new TypeProcessor(), + 'typeResolver' => $attributeTypeResolverMock, + 'fieldNamer' => $fieldNamerMock, + ] + ); + } + + public function testGetMethodReturnType() + { + $this->assertEquals( + 'string', + $this->model->getMethodReturnType('Magento\Framework\Reflection\FieldNamer', 'getFieldNameForMethodName') + ); + $this->assertEquals( + 'mixed', + $this->model->getMethodReturnType('Magento\Framework\Reflection\TypeCaster', 'castValueToType') + ); + $this->assertEquals( + 'array', + $this->model->getMethodReturnType('Magento\Framework\Reflection\MethodsMap', 'getMethodsMap') + ); + } + + public function testGetMethodsMap() + { + $methodsMap = $this->model->getMethodsMap('Magento\Framework\Reflection\MethodsMap'); + $this->assertEquals( + $methodsMap, + [ + 'getMethodReturnType' => [ + 'type' => 'string', + 'isRequired' => true, + 'description' => null, + 'parameterCount' => 2, + ], + 'getMethodsMap' => [ + 'type' => 'array', + 'isRequired' => true, + 'description' => "<pre> Service methods' reflection data stored in cache as 'methodName' => " + . "'returnType' ex. [ 'create' => '\Magento\Customer\Api\Data\Customer', 'validatePassword' " + . "=> 'boolean' ] </pre>", + 'parameterCount' => 1, + ], + 'isMethodValidForDataField' => [ + 'type' => 'bool', + 'isRequired' => true, + 'description' => null, + 'parameterCount' => 2, + ], + 'isMethodReturnValueRequired' => [ + 'type' => 'bool', + 'isRequired' => true, + 'description' => null, + 'parameterCount' => 2, + ], + ] + ); + } + + /** + * @param string $type + * @param string $methodName + * @param bool $expectedResult + * @dataProvider isMethodValidForDataFieldProvider + */ + public function testIsMethodValidForDataField($type, $methodName, $expectedResult) + { + $this->assertEquals($this->model->isMethodValidForDataField($type, $methodName), $expectedResult); + } + + /** + * @return array + */ + public function isMethodValidForDataFieldProvider() + { + return [ + 'MethodsMap#isMethodValidForDataField' => [ + 'Magento\Framework\Reflection\MethodsMap', + 'isMethodValidForDataField', + false, + ], + 'DataObject#getAttrName' => [ + 'Magento\Framework\Reflection\Test\Unit\DataObject', + 'getAttrName', + true, + ], + 'DataObject#isActive' => [ + 'Magento\Framework\Reflection\Test\Unit\DataObject', + 'isActive', + true, + ], + ]; + } + + /** + * @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); + } + + /** + * @return array + */ + public function isMethodReturnValueRequiredProvider() + { + return [ + 'DataObject#getAttrName' => [ + 'Magento\Framework\Reflection\Test\Unit\DataObject', + 'getAttrName', + true, + ], + 'DataObject#isActive' => [ + 'Magento\Framework\Reflection\Test\Unit\DataObject', + 'isActive', + true, + ], + 'FieldNamer#getFieldNameForMethodName' => [ + 'Magento\Framework\Reflection\FieldNamer', + 'getFieldNameForMethodName', + false, + ], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeCasterTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeCasterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8f7afdbbedcb05e103f92c6c982d5a0686cf7481 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeCasterTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Reflection\Test\Unit; + +use Magento\Framework\Reflection\TypeCaster; + +/** + * Type caster Test + */ +class TypeCasterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var TypeCaster + */ + private $model; + + /** + * Set up helper. + */ + public function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject('Magento\Framework\Reflection\TypeCaster'); + } + + /** + * @param mixed $origValue + * @param string $typeToCast + * @param mixed $expectedValue + * @dataProvider typeCastValueProvider + */ + public function testCastValues($origValue, $typeToCast, $expectedValue) + { + $value = $this->model->castValueToType($origValue, $typeToCast); + $this->assertTrue($value === $expectedValue); + } + + /** + * @return array + */ + public function typeCastValueProvider() + { + return [ + 'null' => [null, 'int', null], + 'int' => ['1', 'int', 1], + 'integer' => ['1', 'integer', 1], + 'string' => ['1', 'string', '1'], + 'bool 0' => ['0', 'bool', false], + 'bool 1' => ['1', 'bool', true], + 'boolean 0' => ['0', 'boolean', false], + 'boolean 1' => ['1', 'boolean', true], + 'true' => ['1', 'true', true], + 'false' => ['0', 'false', false], + 'float' => ['1.03', 'float', 1.03], + 'double' => ['1.30', 'double', 1.30], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Reflection/TypeCaster.php b/lib/internal/Magento/Framework/Reflection/TypeCaster.php new file mode 100644 index 0000000000000000000000000000000000000000..185b86c1d880b14061f3271da5de4885bf56cf29 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/TypeCaster.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Reflection; + +/** + * Casts values to the type given. + */ +class TypeCaster +{ + /** + * Cast the output type to the documented type. This helps for consistent output (e.g. JSON). + * + * @param mixed $value + * @param string $type + * @return mixed + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function castValueToType($value, $type) + { + if ($value === null) { + return null; + } + + if ($type === "int" || $type === "integer") { + return (int)$value; + } + + if ($type === "string") { + return (string)$value; + } + + if ($type === "bool" || $type === "boolean" || $type === "true" || $type == "false") { + return (bool)$value; + } + + if ($type === "float") { + return (float)$value; + } + + if ($type === "double") { + return (double)$value; + } + + return $value; + } +} diff --git a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php index 79cd652f4b22e85a9abc8299e6d6180f4e6037d0..1bf5137218bf4de0d49039d61593c1730a9d871c 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php @@ -8,6 +8,7 @@ namespace Magento\Framework\Webapi; use Magento\Framework\Api\AbstractExtensibleObject; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\Reflection\MethodsMap; /** * Data object converter for REST @@ -19,12 +20,21 @@ class ServiceOutputProcessor */ protected $dataObjectProcessor; + /** + * @var MethodsMap + */ + protected $methodsMapProcessor; + /** * @param DataObjectProcessor $dataObjectProcessor + * @param MethodsMap $methodsMapProcessor */ - public function __construct(DataObjectProcessor $dataObjectProcessor) - { + public function __construct( + DataObjectProcessor $dataObjectProcessor, + MethodsMap $methodsMapProcessor + ) { $this->dataObjectProcessor = $dataObjectProcessor; + $this->methodsMapProcessor = $methodsMapProcessor; } /** @@ -44,7 +54,7 @@ class ServiceOutputProcessor public function process($data, $serviceClassName, $serviceMethodName) { /** @var string $dataType */ - $dataType = $this->dataObjectProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); + $dataType = $this->methodsMapProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); if (is_array($data)) { $result = []; $arrayElementType = substr($dataType, 0, -2);