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

<?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('http://example.com/'));
    }
  }

  /**
   * {@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:

<?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('http://example.com/'));
    }
  }

  public function redirectBeforeWithoutCache(GetResponseEvent $event) {
    if ($event->getRequest()->query->get('redirect-me')) {
      $event->setResponse(new RedirectResponse('http://example.com/'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = array('checkForRedirection');
    $events[KernelEvents::REQUEST][] = array('redirectBeforeWithoutCache', 300);
    return $events;
  }

}

В методе getSubscribedEvents() мы зарегистрировали ответ на тоже событие KernelEvents::REQUEST, только в этом случае мы установили приоритет 300, что позволяет сработать этому методу раньше, чем методы которые сработают для этого события, но у которых меньший приоритет. Теперь редирект сработает даже для закешированной страницы.

В друпале довольно много событий, которые можно добавить в Subscriber, больше примеров вы сможете найти в официальной документации:

https://www.drupal.org/docs/8/creating-custom-modules/event-systems-overview-how-to-subscribe-to-and-dispatch-events