1.4 Продемонструйте як використовувати ін'єкції залежностей (Dependecy Injection)
💡НЕОБХІДНО ЗАПАМʼЯТАТИ:
- Ін'єкція залежностей (Dependency Injection) - це спосіб надати класу те, що йому потрібно для функціонування.
ObjectManager
- це внутрішнє сховище об'єктів Magento, до якого рідко слід звертатися безпосередньо.ObjectManager
робить можливим реалізацію принципу Composition over Inheritance.- Ін'єкція залежностей полегшує тестування, робить додаток більш конфігурованим і надає можливості для потужних функцій, таких як плагіни.
Magento дуже легко налаштовується. Концепція "Magento's Dependency Injection" охоплює це і дозволяє отримати великий контроль.
Ін'єкція залежностей - це буквально впорскування залежностей класу в методи конструктора або сеттера.
Алан Кент має чудову статтю про ін'єкцію залежностей та її переваги.
Принадність ін'єкції залежності полягає в тому, що дуже легко побачити, що потрібно вашому класу з першого погляду. Ви будуєте свій клас навколо класу або інтерфейсу, який ін'єктується. Вам все одно, який клас або інтерфейс ін'єктується.
Опишіть підхід і архітектуру ін'єкції залежностей Magento.
Простий приклад constructor:
Як реалізуються об'єкти в Magento?
Створення класу під час ін'єкції.
Створення класу за допомогою фабрики.
Чому важливо мати централізований процес створення екземплярів об'єктів?
Визначте, як використовувати конфігураційні файли DI для налаштування Magento.
Плагіни (Plugins).
Преференси (Preferences).
Віртуальні типи (Virtual Types).
Налаштування аргументів / Аргументи конструктора.
Як ви можете перевизначити нативний клас, вставити свій клас в інший об'єкт та використати інші техніки, доступні у di.xml (такі як як virtualTypes)?
Опишіть підхід і архітектуру ін'єкції залежностей Magento.
Фреймворк ін'єкції залежностей Magento унікальний тим, що він працює дуже автоматично. Багато інших фреймворків вимагають принаймні певного рівня конфігурації, щоб почати роботу. Magento надає способи налаштовувати і регулювати ін'єкцію залежностей "на льоту".
Magento використовує ін'єкцію конструктора: тобто, всі залежності вказуються як аргументи у функції __construct()
.
Перш ніж ми продовжимо, потрібно зазначити: дуже погана практика - безпосередньо використовувати ObjectManager
(основний клас, який обробляє впровадження залежностей). Це суперечить стандартам Magento (за винятком кількох випадків).
Простий приклад constructor:
namespace CompanyName\ModuleName\Component;
use Magento\Framework\App\Request\DataPersistorInterface;
class Details
{
public function __construct(
private readonly DataPersistorInterface $dataPersistor
) {
}
}
Ми визначили конструктор для вищенаведеного класу CompanyName\ModuleName\Component\Details
. Ми завантажуємо запис посилання до класу DataPersistorInterface
. Це відбувається шляхом встановлення типу класу та імені аргументу у конструкторі.
Контейнер DI Magento містить список об'єктів. Кожного разу, коли об'єкт створюється, він додається до цього контейнера. Кожного разу, коли об'єкт запитується, він завантажується з цього контейнера. У PHP об'єкти є посиланнями. Якщо ви не клонуєте об'єкт, куди б ви не передали об'єкт (як аргумент), ви будете посилатися на цей самий об'єкт.
🔗 Корисні посилання:
Для об'єктів, створення яких може зайняти багато часу, Magento надає проксі. Проксі ліниво завантажує (lazy load) клас. Щоб скористатися цим, вкажіть клас на кшталт Magento\Catalog\Model\Product\Proxy
в di.xml
. Проксі НЕ повинні бути вказані в методі __construct
.
Для об'єктів, які мають бути новими кожного разу, коли вони використовуються, Magento використовує фабрики. Щоб використати фабрику, вкажіть назву класу або інтерфейсу і додайте Factory в кінці. Наприклад: Magento\Catalog\Api\Data\ProductInterfaceFactory
. Magento знайде преференс (preference) для ProductInterface
(за замовчуванням Magento\Catalog\Model\Product.php
) і створить фабрику для цього класу. Фабрика має один загальнодоступний метод create. Виклик цього методу створює новий екземпляр потрібного класу. Magento створює ці класи автоматично. Якщо ви хочете, ви можете створити фабричні класи. Ось приклад: vendor/magento/module-paypal/Model/IpnFactory.php
.
📌 MAGENTO 1 vs MAGENTO 2
Ключова концепція, яку слід розуміти, порівнюючи Magento 1 і Magento 2, полягає в наступному:
- У Magento 1, коли вам був потрібен об'єкт продукту, ви йшли і брали його (
Mage::getModel('catalog/product');
). - У Magento 2 ви отримуєте об'єкт у конструкторі. Якщо вам потрібен новий/чистий об'єкт, ви обираєте клас Фабрики (Factory).
Як реалізуються об'єкти в Magento?
Оскільки ін'єкція залежностей (dependecy injection) відбувається автоматично в конструкторі, Magento повинна обробляти створення класів. Таким чином, створення класу відбувається або під час ін'єкції (injection), або у фабриці (factory).
Створення класу під час ін'єкції.
Чудовий спосіб подивитися на це крок за кроком, це поставити точку зупинку (breakpoint) у vendor/magento/framework/App/Router/Base.php
::matchAction
на лінії що містить рядок $this->actionFactory->create()
.
Першим кроком у процесі є знаходження менеджером об'єктів (object manager) належного типу класу. Якщо запитується інтерфейс, сподіваємося, що запис у di.xml
надасть конкретний клас для інтерфейсу (якщо ні, то буде згенеровано виняток).
Режим розгортання (deploy mode) (bin/magento deploy:mode:show
) визначає, який саме завантажувач класу використовується.
- Developer:
vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php
- Production:
vendor/magento/framework/ObjectManager/Factory/Dynamic/Production.php
Завантажуються параметри для конструктора. Потім ці параметри рекурсивно аналізуються. Завантажуються не тільки залежності для початково запитуваного класу, але й залежності залежностей.
Метафорою цього може бути дерево. У дерева є стовбур, а потім гілки. Стовбур представляє тип об'єкта. Але цей об'єкт має залежності, які продовжують розгалужуватися і підніматися вгору по дереву. Зрештою, у вас є всі гілки і все листя, що представляють всі класи (залежності), які потрібні вашому класу для виконання своїх функцій.
Створення класу за допомогою фабрики.
Бувають випадки, коли вам не потрібен спільний об'єкт. Хорошим прикладом може бути створення нового продукту і збереження його в базі даних. Якщо ви запросили екземпляр Magento\Catalog\Api\Data\ProductInterface
, цей об'єкт буде доступний з усіма іншими запитами для цього екземпляра. Таким чином, виклик будь-яких сеттер (setter) методів для цього об'єкта призводить до зміни цього значення у всіх інших запитах для цього екземпляра.
Якщо об'єкт, що ін'єктується (inject), не зберігає дані в класі (тобто всі дані є тимчасовими і тільки проходять через клас), вам, найімовірніше, не потрібно завантажувати екземпляр класу з фабрикою. Якщо вам потрібно використовувати клас, який зберігає дані в класі (наприклад, у масиві _data
), вам, найімовірніше, потрібно використовувати фабрику.
Інший спосіб подивитися на це - замість того, щоб викликати new ClassName()
в коді, ін'єктуйте (inject) екземпляр ClassNameFactory
в конструктор і викличте $classNameFactory->create()
.
Щоб побачити автоматично згенеровані фабрики, подивіться в папку /generated
. Якщо вам потрібно створити власну фабрику, не соромтеся скопіювати одну з них як шаблон, змінивши простір імен, ім'я класу і, ймовірно, параметри функції create.
🔗 Корисні посилання:
Чому важливо мати централізований процес створення екземплярів об'єктів?
Централізований процес створення об'єктів значно спрощує тестування. Він також надає простий інтерфейс для заміни об'єктів, а також модифікації існуючих.
Визначте, як використовувати конфігураційні файли DI для налаштування Magento.
Ви повинні бути добре знайомі з di.xml
і як ним користуватися.
Плагіни (Plugins).
Плагіни дозволяють обгортати загальнодоступні (public) функції іншого класу, додавати метод before для зміни вхідних аргументів або метод after для зміни вихідних даних.
Докладніше про це йтиметься в наступному розділі.
Наприклад: vendor/magento/module-catalog/etc/di.xml
(пошукайте "plugins").
Преференси (Preferences).
Преференси використовуються для заміни цілих класів. Вони також можуть бути використані, щоб вказати конкретний клас для інтерфейсу. Якщо ви створюєте сервісний контакт (service contract) репозиторія (repository) у вашій теці /Api
і конкретний клас у теці /Model
, ви можете створити преференс ось так:
<preference for="CompanyName\ModuleName\Api\TestRepositoryInterface" type="CompanyName\ModuleName\Model\TestRepository" />
Віртуальні типи (Virtual Types).
Віртуальний тип дозволяє розробнику створити екземпляр існуючого класу, який має власні аргументи конструктора. Це корисно у випадках, коли вам потрібен "новий" клас тільки тому, що потрібно змінити аргументи конструктора.
Це часто використовується в Magento для зменшення надлишкових класів PHP.
🔗 Корисні посилання:
Налаштування аргументів / Аргументи конструктора.
Можна змінити об'єкти, що ін'єктуються (inject) у конкретні класи, вказавши ім'я аргументу, щоб пов'язати його з новим класом.
Тепер, з Magento 2, ви можете ін'єктувати (inject) свій власний клас у будь-який інший конструктор класів у di.xml
📌 MAGENTO 1
У Magento 1, якщо ви хотіли внести зміни до класу, ви переписували цей клас, і ця зміна була постійною. Всі класи, які взаємодіяли зі зміненим класом, повинні були використовувати цей змінений клас. Це було тому, що в Magento 1 не було ін'єкції залежностей.
Приклад:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="CompanyName\ModuleName\Algorithms\Primary">
<arguments>
<argument name="basedOn" xsi:type="string">sort_order</argument>
</arguments>
</type>
</config>
🔗 Корисні посилання:
Як ви можете перевизначити нативний клас, вставити свій клас в інший об'єкт та використати інші техніки, доступні у di.xml (такі як як virtualTypes)?
Перевизначення нативного класу: використовуйте <preference/>
, щоб вказати ім'я існуючого класу (зворотна коса риска \ не є обов'язковою) та клас, який буде перевизначено.
Ін'єкція вашого класу в інший об'єкт: використовуйте <type/>
з агрументом <argument xsi:type="object">\Path\To\Your\Class</argument>
в вузлі (node) <arguments/>
.
Інші варіанти використання di.xml
наведено вище.