Внедрение зависимостей
Внедрение зависимостей — способ передать классу объекты, которые ему нужны для работы. В Magento объекты не создают словом new: класс перечисляет нужное в конструкторе, а специальная часть ядра — менеджер объектов — создаёт и передаёт всё сама. Вы уже пользовались этим, когда действие получало ResultFactory.
Свой класс-служба
Создадим класс, который формирует приветствие и пишет строку в журнал. Классы с логикой кладут в папку Model модуля; файл создают вручную.
// app/code/Acme/Hello/Model/Greeter.php
namespace Acme\Hello\Model;
use Psr\Log\LoggerInterface;
class Greeter
{
public function __construct(private LoggerInterface $logger) {}
public function greet(string $name): string
{
$this->logger->info('Приветствие для ' . $name);
return 'Здравствуйте, ' . $name . '!';
}
}Класс просит журнал по типу LoggerInterface. Регистрировать Greeter нигде не нужно: Magento создаст его, когда он кому-то понадобится.
Использование
Чтобы получить Greeter, другой класс тоже просит его в конструкторе:
// app/code/Acme/Hello/ViewModel/Greeting.php
namespace Acme\Hello\ViewModel;
use Acme\Hello\Model\Greeter;
use Magento\Framework\View\Element\Block\ArgumentInterface;
class Greeting implements ArgumentInterface
{
public function __construct(private Greeter $greeter) {}
public function getGreeting(): string
{
return $this->greeter->greet('Анна');
}
}Выведите в шаблоне $escaper->escapeHtml($viewModel->getGreeting()), удалите содержимое папки generated и откройте /hello. На странице появится «Здравствуйте, Анна!», а в файле var/log/system.log — новая строка.
Как Magento выбирает объект
Менеджер объектов смотрит на типы параметров конструктора. Если тип — класс, создаётся объект этого класса. Если тип — интерфейс, нужно правило, какой класс за ним стоит. Такое правило называют предпочтением (preference) и записывают в файле di.xml. Для LoggerInterface оно уже есть в ядре.
Свой интерфейс и предпочтение
Интерфейсы модуля по соглашению лежат в папке Api:
// app/code/Acme/Hello/Api/GreeterInterface.php
namespace Acme\Hello\Api;
interface GreeterInterface
{
public function greet(string $name): string;
}Допишите к классу Greeter слова implements \Acme\Hello\Api\GreeterInterface и создайте файл etc/di.xml:
<?xml version="1.0"?>
<!-- app/code/Acme/Hello/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Acme\Hello\Api\GreeterInterface" type="Acme\Hello\Model\Greeter"/>
</config>Теперь в конструкторе Greeting можно просить GreeterInterface — придёт объект Greeter. После правки di.xml выполните bin/magento cache:clean. Польза в том, что другой модуль может подставить свою реализацию, не меняя ваш код.
app/code/Acme/Hello/
├── Api/
│ └── GreeterInterface.php новый файл
├── Model/
│ └── Greeter.php новый файл
├── ViewModel/Greeting.php
└── etc/
├── di.xml новый файл
└── module.xmlГде действует di.xml
| Файл | Где действует |
|---|---|
etc/di.xml | Везде |
etc/frontend/di.xml | Только на витрине |
etc/adminhtml/di.xml | Только в панели управления |
Сгенерированные классы
Для работы внедрения Magento создаёт вспомогательные классы в папке generated. В режиме разработчика это происходит на лету, поэтому после изменения конструктора папку достаточно очистить. На рабочем сервере всё собирают заранее командой bin/magento setup:di:compile.
Частая ошибка
Вызов ObjectManager::getInstance()->get(...) прямо в своём классе работает, но ломает всю идею: зависимости становятся невидимыми, их нельзя заменить через di.xml и проверить тестом. Всё, что нужно классу, перечисляйте в конструкторе.