12.12. Event Dispatcher, кастомный код для определенных событий.
Система events позволяет строить более сложные системы с возможностью изменения функционала с помощью кастомного кода по определенным событиям. Многие хуки из Drupal 7 были заменены event'ами. Это позволило унифицировать работу многих частей друпала и дополнительных контрибных модулей. Сама система events пришла из Symfony и состоит из следующих частей:
Event Subscribers - "Подписчики" на определенные события, это функции или методы, которые срабатывают по определенным событиям. В коде является классом, который реализует класс:
\Symfony\Component\EventDispatcher\EventSubscriberInterface
Event Registry - Собирает и сортирует по очередности срабатывания Event Subscribers. Registry для subscribers хранимых в объекте Event Dispatcher'а как массив ключ-значение имени события и приоритет события (порядок). Когда событие регистрируется как service, событие регистрируется как глобально доступный dispatcher.
Event Dispatcher - Механизм в котором событие срабатывает и позволяет вызывать в нужный момент Event Subscribers. Главным образом по меньшей мере один из экземляров Event Dispatcher'a представлен как service. Класс Event Dispatcher'a реализует: \Symfony\Component\EventDispatcher\EventDispatcherInterface.
Event Context - Многие события требуют специфического набора данных, что может быть вазжно для subscriber'а на event. Это может быть простое значение переданное в Event Subscriber, а может быть и класс который содержит нужные данные. Класс Event Context расширяет класс: \Symfony\Component\EventDispatcher\Event.
Давайте разберем примеры работы с событиями, чтобы стало понятно как это работает.
Я добавил весь код на github в модуль drupalbook_examples, вы можете скачать модуль и добавить его к себе на сайт:
https://github.com/levmyshkin/drupalbook8
В Drupal 8 больше нет hook_init():
https://www.drupal.org/node/2013014
Теперь выполнить нужный код при загрузке страницы можно с помощью Event Subscriber'a:
modules/custom/drupalbook_examples/drupalbook_examples.services.yml
services:
drupalbook_examples.event_subscriber:
class: Drupal\drupalbook_examples\EventSubscriber\DrupalbookExamplesSubscriber
tags:
- {name: event_subscriber}
Для того чтобы подключить Event Subscriber нужно добавить сервис в файл модуля *.services.yml. Здесь мы описываем какой класс будет Event subscriber'ом и пишем в тегах имя event_subscriber. Теперь нужно создать класс event subscriber:
modules/custom/drupalbook_examples/src/EventSubscriber/DrupalbookExamplesSubscriber.php
namespace Drupal\drupalbook_examples\EventSubscriber;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DrupalbookExamplesSubscriber implements EventSubscriberInterface {
public function checkForRedirection(GetResponseEvent $event) {
if ($event->getRequest()->query->get('redirect-me')) {
$event->setResponse(new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE])));
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('checkForRedirection');
return $events;
}
}
Давайте разберем основные моменты этого кода. Класс нашего event subscriber реализует интерфейс EventSubscriberInterface, основным методом этого интерфейса является getSubscribedEvents(), он определяет на какие события будет реагировать наш subscriber и какие методы он будет вызывать в случае если событие произойдет. В нашем случает событие произойдет при самом начале работы друпала после обработки запроса. Здесь мы вызывает метод checkForRedirection() если происходит событие. В самом методе если у нас есть get параметр redirect-me, то перенаправляет пользователя на сайт example.com.
Этот код будет работать, всегда когда вы будете загружать страницу, ну или почти всегда. Дело в том, если ваша страница закеширована, то она может быть отдана из кеша и друпал не будет отрабатывать все куски кода и сразу сработает кеш. В этом случае можно использовать аналог хука hook_boot(), который также как и hook_init() удален из Drupal 8:
https://www.drupal.org/node/1909596
Давайте добавим еще метод redirectBeforeWithoutCache()
modules/custom/drupalbook_examples/src/EventSubscriber/DrupalbookExamplesSubscriber.php:
namespace Drupal\drupalbook_examples\EventSubscriber;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DrupalbookExamplesSubscriber implements EventSubscriberInterface {
public function checkForRedirection(GetResponseEvent $event) {
if ($event->getRequest()->query->get('redirect-me')) {
$event->setResponse(new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE])));
}
}
public function redirectBeforeWithoutCache(GetResponseEvent $event) {
if ($event->getRequest()->query->get('redirect-me')) {
$event->setResponse(new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE])));
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('checkForRedirection');
$events[KernelEvents::REQUEST][] = array('redirectBeforeWithoutCache', 300);
return $events;
}
}
В методе getSubscribedEvents() мы зарегистрировали ответ на тоже событие KernelEvents::REQUEST, только в этом случае мы установили приоритет 300, что позволяет сработать этому методу раньше, чем методы которые сработают для этого события, но у которых меньший приоритет. Теперь редирект сработает даже для закешированной страницы.
В друпале довольно много событий, которые можно добавить в Subscriber, больше примеров вы сможете найти в официальной документации: