Magento 2: type i virtualType
Magento 2 daje nam dużą elastyczność jeśli chodzi o nadpisywanie elementów aplikacji. Temat, który chce tu poruszyć to modyfikacja zależności wstrzykiwanych do konstruktora klas. W celu podmienienia/określenia argumentu konstruktora wybranej klasy wystarczy stworzyć konfigurację w pliku di.xml, a następnie ustawić ją za pomocą węzła <type>. Czasami potrzebujemy nie tyle określić klasę jako zależność, a jedynie podmienić jej argumenty konstruktora. W celu nie potrzebujemy tworzyć fizycznie samej klasy, a jedynie jej nową wersję – efekt ten otrzymamy przy ustawianiu węzła <virtualType>. Po krótce przedstawię jak i kiedy możemy stosować type i virtualType.
Węzeł type
Węzeł <type> zezwala na manipulowanie zależnościami wstrzykiwanymi do klasy konstruktora. W samym Magento jest trochę przykładów, zaprezentuje parę z nich. Taki rodzaj konfiguracji możemy używać tylko w di.xml.
Przykład: dodawanie nowego rutera
W Magento istnieje klasa, która posiada listę wszystkich ruterów obsługujących żądania. Przykład spersonalizowanego rutera znajdziemy dla stron typu cms. W pliku vendor/magento/module-cms/etc/frontend/di.xml możemy odnaleźć taką definicję:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Framework\App\RouterList"> <arguments> <argument name="routerList" xsi:type="array"> <item name="cms" xsi:type="array"> <item name="class" xsi:type="string">Magento\Cms\Controller\Router</item> <item name="disable" xsi:type="boolean">false</item> <item name="sortOrder" xsi:type="string">60</item> </item> </argument> </arguments> </type> </config> |
Mamy określony type dla klasy Magento\Framework\App\RouterList. Dla jej argumentu konstruktora $routerList, który jest tablicą, dodajemy nowy element, tablicę z kluczem „cms„. Dodana tablica posiada również swoje elementy. Element tablicy jest określany za pomocą węzła <item>.
Magento „zbiera” konfigurację z innych modułów i łączy poszczególne definicje w jedną tablicę. Tym sposobem, możemy w swoim module dodać własny router czy wyłączyć wybrany.
Przykład: klasa Proxy
Stosujemy <type> również w przypadku, gdy w wybranej klasie chcemy podmienić zależność na klasę typu proxy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Tax\Model\Calculation"> <arguments> <argument name="customerAccountManagement" xsi:type="object">Magento\Customer\Api\AccountManagementInterface\Proxy</argument> <argument name="customerGroupManagement" xsi:type="object">Magento\Customer\Api\GroupManagementInterface\Proxy</argument> <argument name="customerGroupRepository" xsi:type="object">Magento\Customer\Api\GroupRepositoryInterface\Proxy</argument> <argument name="customerRepository" xsi:type="object">Magento\Customer\Api\CustomerRepositoryInterface\Proxy</argument> </arguments> </type> <!-- (...) --> </config> |
Powyższy przykład pochodzi z modułu Magento_Tax, jest to plik z vendor/magento/module-tax/etc/frontend/di.xml. Podstawowe typy argumentów konstruktora klasy Magento\Tax\Model\Calculation są nienaruszone.
Węzeł virtualType
Z pomocą <virtualType> możemy modyfikować istniejące klasy, a następnie wstrzyknąć je tam, gdzie potrzebujemy za pomocą <type>. Nie możemy w ten sposób stworzonej klasy użyć bezpośrednio w kodzie php!
W pliku di.xml stwórzmy najprostszy przykład, gdzie nie modyfikujemy parametrów konstruktora:
1 |
<virtualType name="Anna/VirtualType/Block/ExtendedSimple" type="Anna/VirtualType/Block/Simple"> |
To tak, jakbyśmy stworzyli klasę Anna/VirtualType/Block/ExtendedSimple:
1 2 3 |
class Anna/VirtualType/Block/ExtendedSimple extends Anna/VirtualType/Block/Simple { } |
Przykład: klasa kolekcji dla grida zbudowanego na komponentach UI
Bardzo często potrzebujemy po prostu swojej wersji klasy w określonych sytuacjach. Jeśli do samej klasy nie potrzebujemy się odnosić bezpośrednio w kodzie to poniższa konfiguracja, która konfiguruje klasę kolekcji dla klasy providera danych dla komponentu grid:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <!--- (...) --> <virtualType name="Magento\Search\Model\ResourceModel\Synonyms\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult"> <arguments> <argument name="name" xsi:type="string">search_synonyms_grid_data_source</argument> <argument name="primaryFieldName" xsi:type="string">group_id</argument> <argument name="requestFieldName" xsi:type="string">id</argument> <argument name="mainTable" xsi:type="string">search_synonyms</argument> <argument name="resourceModel" xsi:type="string">Magento\Search\Model\ResourceModel\SynonymGroup</argument> </arguments> </virtualType> <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory"> <arguments> <argument name="collections" xsi:type="array"> <item name="search_synonyms_grid_data_source" xsi:type="string">Magento\Search\Model\ResourceModel\Synonyms\Grid\Collection</item> </argument> </arguments> </type> <!--- (...) --> </config> |
Przykład pochodzi z modułu Magento_Search, z pliku vendor/magento/module-search/etc/di.xml. Bardziej szczegółowy opis takiej konfiguracji znajdziesz w tej notce.
Rodzaje argumentów, jakie obsłużymy w di.xml
Typu argumentów xsi:type jakie możemy obsłużyć, znajdują się w plikach:
- plik vendor/magento/framework/Data/etc/argument/types.xsd,
- plik vendor/magento/framework/ObjectManager/etc/config.xsd.
Podstawowa lista jest następująca:
- array — klasa Magento\Framework\Data\Argument\Interpreter\ArrayType,
- string — klasa Magento\Framework\Data\Argument\Interpreter\BaseStringUtils,
- boolean — klasa Magento\Framework\Data\Argument\Interpreter\Boolean,
- object — klasa Magento\Framework\Data\Argument\Interpreter\DataObject,
- number — klasa Magento\Framework\Data\Argument\Interpreter\Number,
- null — klasa Magento\Framework\Data\Argument\Interpreter\NullType,
- init_parameter — klasa Magento\Framework\App\Arguments\ArgumentInterpreter,
- const — klasa Magento\Framework\Data\Argument\Interpreter\Constant.
Klasa interpretera implementuje poniższy interfejs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Data\Argument; /** * Interface that encapsulates complexity of expression computation * * @api * @since 100.0.2 */ interface InterpreterInterface { /** * Compute and return effective value of an argument * * @param array $data * @return mixed * @throws \InvalidArgumentException * @throws \UnexpectedValueException */ public function evaluate(array $data); } |
Możemy sprawdzić poszczególne klasy, aby dowiedzieć się jakie parametry dla węzła <argument> możemy dodatkowo ustawić. Jeśli używamy xsi:type=”object”, metoda evaluate klasy Magento\Framework\Data\Argument\Interpreter\DataObject:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * Compute and return effective value of an argument * * @param array $data * @return array * @throws \InvalidArgumentException * @throws \UnexpectedValueException */ public function evaluate(array $data): array { $result = ['instance' => $data['value']]; if (array_key_exists('sortOrder', $data)) { $result['sortOrder'] = $data['sortOrder']; } if (isset($data['shared'])) { $result['shared'] = $this->booleanUtils->toBoolean($data['shared']); } return $result; } |
Wewnętrzna zawartość węzła <argument> to będzie nazwa klasy, którą chcemy przekazać. Możemy dodatkowo określić takie atrybuty jak:
- shared – który określa cykl życia obiektu, czy instancja ma być współdzielona czy tworzona za każdym razem od nowa.
- sortOrder – kolejność.
Jak Magento 2 inicjalizuje interpretery dla argumentów
Główna lista interpreterów inicjalizowana jest w klasie Magento\Framework\App\ObjectManagerFactory za pomocą metody createArgumentInterpreter():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * Return newly created instance on an argument interpreter, suitable for processing DI arguments * * @param \Magento\Framework\Stdlib\BooleanUtils $booleanUtils * @return \Magento\Framework\Data\Argument\InterpreterInterface */ protected function createArgumentInterpreter( \Magento\Framework\Stdlib\BooleanUtils $booleanUtils ) { $constInterpreter = new \Magento\Framework\Data\Argument\Interpreter\Constant(); $result = new \Magento\Framework\Data\Argument\Interpreter\Composite( [ 'boolean' => new \Magento\Framework\Data\Argument\Interpreter\Boolean($booleanUtils), 'string' => new \Magento\Framework\Data\Argument\Interpreter\BaseStringUtils($booleanUtils), 'number' => new \Magento\Framework\Data\Argument\Interpreter\Number(), 'null' => new \Magento\Framework\Data\Argument\Interpreter\NullType(), 'object' => new \Magento\Framework\Data\Argument\Interpreter\DataObject($booleanUtils), 'const' => $constInterpreter, 'init_parameter' => new \Magento\Framework\App\Arguments\ArgumentInterpreter($constInterpreter), ], \Magento\Framework\ObjectManager\Config\Reader\Dom::TYPE_ATTRIBUTE ); // Add interpreters that reference the composite $result->addInterpreter('array', new \Magento\Framework\Data\Argument\Interpreter\ArrayType($result)); return $result; } |
To nie jest jedyne miejsce, gdzie Magento tworzy liste interpreterów. Do klasy Magento\Framework\Data\Argument\Interpreter\Composite możemy dodać nowy interpreter. Przykład możemy znaleźć w module Magento_Store, gdzie w pliku di.xml mamy taki fragment:
1 2 3 4 5 6 7 |
<type name="Magento\Framework\Data\Argument\Interpreter\Composite"> <arguments> <argument name="interpreters" xsi:type="array"> <item name="serviceUrl" xsi:type="object">Magento\Store\Model\Argument\Interpreter\ServiceUrl</item> </argument> </arguments> </type> |
Dodany interpreter, klasa Magento\Store\Model\Argument\Interpreter\ServiceUrl, generuje adres url serwisu, który dodaje odpowiedni przedrostek do url, tutaj rest/default/V1.
Inny przykład to klasa Magento\Framework\View\Layout\Reader\Block, która jest odpowiedzialna odczyt struktury bloku. Jednym z argumentów jakie przyjmuje jej konstruktor to $argumentInterpreter, który przyjmuje za argument klasę typu virtual, której nadano nazwę layoutArgumentReaderInterpreter. Taką konfigurację mamy zdefiniowaną w pliku app/etc/di.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <!-- (...) --> <virtualType name="layoutArgumentReaderInterpreter" type="Magento\Framework\Data\Argument\Interpreter\Composite"> <arguments> <argument name="interpreters" xsi:type="array"> <item name="options" xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Options</item> <item name="array" xsi:type="object">layoutArrayArgumentReaderInterpreterProxy</item> <item name="boolean" xsi:type="object">Magento\Framework\Data\Argument\Interpreter\Boolean</item> <item name="number" xsi:type="object">Magento\Framework\Data\Argument\Interpreter\Number</item> <item name="string" xsi:type="object">Magento\Framework\Data\Argument\Interpreter\StringUtils</item> <item name="null" xsi:type="object">Magento\Framework\Data\Argument\Interpreter\NullType</item> <item name="object" xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Passthrough</item> <item name="url" xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Passthrough</item> <item name="helper" xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Passthrough</item> </argument> <argument name="discriminator" xsi:type="const">Magento\Framework\View\Model\Layout\Merge::TYPE_ATTRIBUTE</argument> </arguments> </virtualType> <!-- (...) --> <type name="Magento\Framework\View\Layout\Reader\Block"> <arguments> <argument name="readerPool" xsi:type="object">blockRenderPool</argument> <argument name="scopeType" xsi:type="const">Magento\Store\Model\ScopeInterface::SCOPE_STORE</argument> <argument name="argumentInterpreter" xsi:type="object">layoutArgumentReaderInterpreter</argument> </arguments> </type> <!-- (...) --> </config> |
Dla zastosowań layoutowych mamy takie typu jak:
- options — klasa Magento\Framework\View\Layout\Argument\Interpreter\Options,
- object – obsługiwany przez inną klasę Magento\Framework\View\Layout\Argument\Interpreter\Passthrough,
- url – klasa Magento\Framework\View\Layout\Argument\Interpreter\Passthrough,
- helper – klasa Magento\Framework\View\Layout\Argument\Interpreter\Passthrough.
Jak również typy, które są określane w konfiguracji za pomocą virtualType.
Różnica pomiędzy typem argumentu const i init_parameter
W module Magento_Store, w pliku vendor/magento/module-store/etc/di.xml możemy znaleźć dwa fragmenty, dwa typu argumentów gdzie przekazujemy do nich wartość stałej.
Przykład z użyciem xsi:type=”const”:
1 2 3 4 5 |
<type name="Magento\Framework\Module\Output\Config"> <arguments> <argument name="scopeType" xsi:type="const">Magento\Store\Model\ScopeInterface::SCOPE_STORE</argument> </arguments> </type> |
Przykład z użycie xsi:type=”init_parameter”:
1 2 3 4 5 6 7 |
<type name="Magento\Store\Model\Store"> <arguments> <argument name="session" xsi:type="object" shared="false">Magento\Framework\Session\Generic\Proxy</argument> <argument name="isCustomEntryPoint" xsi:type="init_parameter">Magento\Store\Model\Store::CUSTOM_ENTRY_POINT_PARAM</argument> <argument name="url" xsi:type="object" shared="false">Magento\Framework\UrlInterface</argument> </arguments> </type> |
Dla obu typów argumentów przekazujemy wartość stałych. Czym one się różnią?
W przypadku typu const, do konstruktora otrzymuje argument o wartości przekazanej stałej.
Jeśli chodzi o argument typu init_parameter menadżer obiektów wykorzystuje nazwę stałej, aby wyciągnąć wartość ze zmiennej globalnej. W przedstawionym przykładzie podana stała Magento\Store\Model\Store::CUSTOM_ENTRY_POINT_PARAM da nam wynik z $_SERVER[’custom_entry_point’].
Zadanie to wykonuje metoda resolveArgument() klasy Magento\Framework\ObjectManager\Factory\AbstractFactory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
/** * Resolve an argument * * @param array $argument * @param string $paramType * @param mixed $paramDefault * @param string $paramName * @param string $requestedType * * @return void * * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function resolveArgument(&$argument, $paramType, $paramDefault, $paramName, $requestedType) { if ($paramType && $argument !== $paramDefault && !is_object($argument)) { if (!isset($argument['instance']) || $argument !== (array)$argument) { throw new \UnexpectedValueException( 'Invalid parameter configuration provided for . $paramName . ' argument of ' . $requestedType ); } $argumentType = $argument['instance']; if (isset($argument['shared'])) { $isShared = $argument['shared']; } else { $isShared = $this->config->isShared($argumentType); } if ($isShared) { $argument = $this->objectManager->get($argumentType); } else { $argument = $this->objectManager->create($argumentType); } } elseif ($argument === (array)$argument) { if (isset($argument['argument'])) { if (isset($this->globalArguments[$argument['argument']])) { $argument = $this->globalArguments[$argument['argument']]; } else { $argument = $paramDefault; } } elseif (!empty($argument)) { $this->parseArray($argument); } } } |
Pluginy w Magento 2 - Web Programming
4 grudnia 2023 @ 03:01
[…] typu VirtualType (określane przez konfigurację w […]