О Dependency Injection в Magento 2

от автора

В предыдущей статье мы рассмотрели базовую структуру модуля в M2: читать тут. В этой статье поговорим об инъекции (внедрении) зависимостей в Magento 2. Как ее использовать и для чего она нужна.

Итак, внедрение зависимостей — это паттерн проектирования предназначенный для того, чтобы предоставлять какому-либо объекту зависимости, необходимые для его работы. Это более совершенная альтернатива наследованию, позволяющая уменьшить связанность классов и компонентов между собой.

В основе этого подхода лежит принцип Dependency Inversion (инверсия зависимостей) из принципов SOLID, который предполагает использование абстракций вместо конкретных классов, где детали реализации должны зависеть от абстракций, а абстракции не должны зависеть от деталей реализации.

ObjectManager

За инъекцию зависимостей в M2 отвечает специальный класс ObjectManager (далее OM), который создается при инициализации приложения. OM вызывается автоматически и его запрещено использовать напрямую, поскольку тогда зависимости перестают быть явными и весь смысл использования Dependency Injection теряется, однако, бывают и исключения, которые будут приведены ниже. А вот и код самого OM:

<?php /**  * Magento object manager. Responsible for instantiating objects taking into account:  * - constructor arguments (using configured, and provided parameters)  * - class instances life style (singleton, transient)  * - interface preferences  *  * Intentionally contains multiple concerns for best performance  *  * Copyright © Magento, Inc. All rights reserved.  * See COPYING.txt for license details.  */ namespace Magento\Framework\ObjectManager;  class ObjectManager implements \Magento\Framework\ObjectManagerInterface {     /**      * @var \Magento\Framework\ObjectManager\FactoryInterface      */     protected $_factory;      /**      * List of shared instances      *      * @var array      */     protected $_sharedInstances = [];      /**      * @var ConfigInterface      */     protected $_config;      /**      * @param FactoryInterface $factory      * @param ConfigInterface $config      * @param array &$sharedInstances      */     public function __construct(FactoryInterface $factory, ConfigInterface $config, &$sharedInstances = [])     {         $this->_config = $config;         $this->_factory = $factory;         $this->_sharedInstances = &$sharedInstances;         $this->_sharedInstances[\Magento\Framework\ObjectManagerInterface::class] = $this;     }      /**      * Create new object instance      *      * @param string $type      * @param array $arguments      * @return mixed      */     public function create($type, array $arguments = [])     {         return $this->_factory->create($this->_config->getPreference($type), $arguments);     }      /**      * Retrieve cached object instance      *      * @param string $type      * @return mixed      */     public function get($type)     {         $type = ltrim($type, '\\');         $type = $this->_config->getPreference($type);         if (!isset($this->_sharedInstances[$type])) {             $this->_sharedInstances[$type] = $this->_factory->create($type);         }         return $this->_sharedInstances[$type];     }      /**      * Configure di instance      * Note: All arguments should be pre-processed (sort order, translations, etc) before passing to method configure.      *      * @param array $configuration      * @return void      */     public function configure(array $configuration)     {         $this->_config->extend($configuration);     } } 

Приложение использует конструктор класса, чтобы получить информацию о зависимостях объекта. При инициализации, OM заполняет объект его зависимостями, определенными в специальном конфигурационном файле di.xml (тут про di.xml) и помещает их в конструктор класса. Если класс не найден, то возвращается исключение (Exception). Аргументы собираются рекурсивно, то есть зависимости заполняются не только на верхнем уровне, но и на всех низ лежащих (зависимости зависимостей) до того момента пока не будут подгружены все.

Итак, основные задачи OM:

  1. Создание объектов в фабриках и прокси-классах

  2. Возвращение singleton-инстанса класса, т.е. единственного экземпляра класса

  3. Автоматическая инициализация аргументов в конструкторах классов

А в файле di.xml:

  1. Происходит настройка OM для работы с зависимостями.

  2. Указывается предпочтительный класс реализации интерфейса (Service Contract), который потом передается в конструктор класса, а также позволяет подменять классы другими.

  3. Можно подменять аргументы классов своими, использовать прокси, виртуальные типы, плагины и т.д.

Когда можно использовать Object Manager?

Бывают случаи, когда использование OM оправдано:

  • при использовании магических методов __wakeup() и __sleep();

  • в тестах;

  • в классах-фабриках и прокси-классах.

Как быть, когда нужно несколько экземпляров класса?

Для таких случаев нужно применять фабрики. Фабрики необходимы для создания объектов.

Для того, чтобы ее использовать, нужно добавить нужный класс через use и в конце дописать ключевое слово Factory вот так:

use Magento\Framework\Controller\Result\RawFactory;

А затем просто подключить данный класс через конструктор.

public function __construct(RawFactory $resultRawFactory) {         $this->resultRawFactory = $resultRawFactory; }

Далее, чтобы получить новый экземпляр класса, необходимо использовать публичный метод фабрики под названием create().

$resultRaw = $this->resultRawFactory->create();

Таким образом, мы получим необходимый экземпляр класса, а M2 сама создаст нужные объекты при переходе в production mode, либо на лету, если активен developer mode и положит его в папку /generated/code. Выглядеть сгенерированный класс будет таким образом:

<?php  namespace Magento\Framework\Controller\Result;  /**  * Factory class for @see \Magento\Framework\Controller\Result\Raw  */ class RawFactory {     /**      * Object Manager instance      *      * @var \Magento\Framework\ObjectManagerInterface      */     protected $_objectManager = null;        /**      * Instance name to create      *      * @var string      */     protected $_instanceName = null;        /**      * Factory constructor      *      * @param \Magento\Framework\ObjectManagerInterface $objectManager      * @param string $instanceName      */     public function __construct(         \Magento\Framework\ObjectManagerInterface $objectManager,          $instanceName = '\\Magento\\Framework\\Controller\\Result\\Raw'     ) {         $this->_objectManager = $objectManager;         $this->_instanceName = $instanceName;     }        /**      * Create class instance with specified parameters      *      * @param array $data      * @return \Magento\Framework\Controller\Result\Raw      */     public function create(array $data = [])     {         return $this->_objectManager->create($this->_instanceName, $data);     } }

Как можно заметить из кода выше, в созданном классе используется object manager. Как упоминалось ранее, в классах-фабриках это допустимо.

Что такое preference?

Preference — это переопределение класса или интерфейса, а также связь интерфейса и его конкретной реализации.

Бывают случаи, когда мы хотим заменить какой-либо класс своей собственной имплементацией, что не очень желательно и лучше использовать плагины, когда это возможно, но тем не менее.

Preference также используется для определения Service Contract, чтобы при подключении класса в конструкторе мы использовали абстракцию, а не конкретный класс.

Выглядит это так, где for — это класс, который переопределяем, а type — класс, которым заменяем:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">     <preference for="Magento\Framework\Search\Adapter\OptionsInterface"                 type="Magento\CatalogSearch\Model\Adapter\Options"/> </config>

Что такое Proxy?

Proxy (Заместитель) — это паттерн проектирования, который предполагает создание суррогата объекта и контроль доступа к нему.

В M2 данный подход применяется, если объект слишком “тяжелый”. Это позволяет подгружать его только в случае необходимости (ленивая загрузка), тем самым ускоряя работу приложения.

Для того, чтобы применить данный подход, нужно выполнить его настройку в di.xml. Например:

<type name="Magento\Framework\DataObject\Copy\Config">     <arguments>         <argument name="dataStorage" xsi:type="object">Magento\Framework\DataObject\Copy\Config\Data\Proxy</argument>     </arguments> </type>

В узле <type> указывается класс, в конструктор которого передается объект, на который будет создан прокси.

Далее в узле <arguments> мы перечисляем необходимые подменяемые на прокси аргументы. В данном случае аргумент dataStorage. В значении ноду указывается путь к классу и в конце через слеш добавляется \Proxy

Изменения выполняются только в конфигурационном файле di.xml. Внутри класса ничего делать не надо. M2 сама сгенерирует нужные классы и положит их в папку /generated

Что такое Virtual Type и Type?

Подход, который позволяет создавать существующий класс с кастомными аргументами конструктора, таким образом, мы создаем как бы “виртуальный” класс, который имеет все те же свойства и методы родительского класса, но с новыми элементами конструктора без какого-либо влияния на изначальный класс, т.е “старый” класс может использоваться как и ранее. Определяется Virtual Type в конфигурационном файле di.xml:

<virtualType name="layoutFileSourceBase"               type="Magento\Framework\View\File\Collector\Base">       <arguments>             <argument name="subDir" xsi:type="string">layout</argument>       </arguments> </virtualType>

В узле <virtualType> атрибут name содержит имя нашего виртуального типа, в атрибуте type указан “родительский” класс, в котором будут заменяться аргументы.

В узле <arguments> перечисляются аргументы, значения которых мы хотим заменить на свои. В данном случае аргумент subDir будет иметь значение layout.

Затем этот виртуальный тип можно подключить в какой-нибудь класс через тот же di.xml через узел <type> в качестве аргумента subject класса Magento\Framework\View\File\Collector\Decorator\ModuleOutput таким образом:

<type name="Magento\Framework\View\File\Collector\Decorator\ModuleOutput">       <arguments>             <argument name="subject" xsi:type="object">layoutFileSourceBase</argument>       </arguments> </type>

В отличие от Virtual Type, Type добавляет аргументы к реальному классу.


Таким образом, мы рассмотрели как в Magento 2 используется Dependency Injection и основные способы его применения на практике. Такой подход упрощает процесс разработки и структурирует и придает формы приложению.


ссылка на оригинал статьи https://habr.com/ru/post/697140/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *