События и наблюдатели
Событие — сообщение «что-то произошло», которое код отправляет в определённый момент: товар открыт, заказ оформлен, покупатель вошёл. Наблюдатель — класс, который подписан на событие и выполняется, когда оно случилось. Так модуль реагирует на действия ядра, не вмешиваясь в его код.
Где лежат файлы
Подписку описывают в файле events.xml, а классы наблюдателей кладут в папку Observer. Оба файла создают вручную.
app/code/Acme/Hello/
├── Observer/
│ └── LogProductView.php новый файл
└── etc/
├── module.xml
└── frontend/
├── events.xml новый файл
└── routes.xmlФайл в etc/frontend действует только на витрине, в etc/adminhtml — только в панели, а etc/events.xml — везде.
Подписка
Когда покупатель открывает страницу товара, ядро отправляет событие catalog_controller_product_view. Подпишемся на него:
<?xml version="1.0"?>
<!-- app/code/Acme/Hello/etc/frontend/events.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_controller_product_view">
<observer name="acme_hello_log_product_view" instance="Acme\Hello\Observer\LogProductView"/>
</event>
</config>Атрибут name у observer — любое уникальное имя, instance — класс наблюдателя.
Наблюдатель
// app/code/Acme/Hello/Observer/LogProductView.php
namespace Acme\Hello\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;
class LogProductView implements ObserverInterface
{
public function __construct(private LoggerInterface $logger) {}
public function execute(Observer $observer): void
{
$product = $observer->getEvent()->getData('product');
$this->logger->info('Просмотр товара: ' . $product->getSku());
}
}Выполните bin/magento cache:clean и откройте страницу любого товара: в файле var/log/system.log появится строка с его артикулом. Вместе с событием передаются данные — здесь это товар под ключом product.
Частые события
| Событие | Когда происходит | Данные |
|---|---|---|
catalog_controller_product_view | Открыта страница товара | product |
catalog_product_save_after | Товар сохранён | product |
checkout_cart_product_add_after | Товар добавлен в корзину | quote_item, product |
sales_order_place_after | Заказ оформлен | order |
customer_login | Покупатель вошёл | customer |
Какие ещё события есть в ядре, проще всего узнать поиском слова dispatch( по папке vendor/magento.
Своё событие
Модуль может отправлять собственные события, чтобы другие модули могли на них реагировать. Для этого в конструкторе просят Magento\Framework\Event\ManagerInterface:
// app/code/Acme/Hello/Model/Greeter.php
use Magento\Framework\Event\ManagerInterface;
public function __construct(private ManagerInterface $eventManager) {}
public function greet(string $name): string
{
$this->eventManager->dispatch('acme_hello_greeted', ['name' => $name]);
return 'Здравствуйте, ' . $name . '!';
}Наблюдатель, подписанный на acme_hello_greeted, получит имя через $observer->getEvent()->getData('name'). Имя события принято начинать с имени модуля.
Плагин или наблюдатель
| Способ | Когда подходит |
|---|---|
| Наблюдатель | Нужно отреагировать на то, что уже произошло |
| Плагин | Нужно изменить аргументы или результат метода |
Частая ошибка
В наблюдателе выполняют тяжёлую работу: отправляют письма, обращаются к стороннему сервису. Наблюдатель срабатывает внутри запроса покупателя, и оформление заказа будет ждать, пока он закончит. Такую работу откладывают — в задачу по расписанию или в очередь сообщений, о которых рассказывает урок о расписании.