В предыдущей статье мы рассмотрели базовую структуру модуля в 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:
-
Создание объектов в фабриках и прокси-классах
-
Возвращение singleton-инстанса класса, т.е. единственного экземпляра класса
-
Автоматическая инициализация аргументов в конструкторах классов
А в файле di.xml:
-
Происходит настройка OM для работы с зависимостями.
-
Указывается предпочтительный класс реализации интерфейса (Service Contract), который потом передается в конструктор класса, а также позволяет подменять классы другими.
-
Можно подменять аргументы классов своими, использовать прокси, виртуальные типы, плагины и т.д.
Когда можно использовать 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/
Добавить комментарий