Статьи

Drupal 8 Hooks и диспетчер событий Symfony

Помните, что из-за процесса разработки Drupal 8 на момент написания этой статьи некоторые части кода могли быть устаревшими. Взгляните на этот репозиторий, в котором я пытаюсь обновить пример кода и заставить его работать с последней версией Drupal 8.

С включением многих компонентов Symfony в Drupal в его 8-й версии мы видим переход от многих Drupalisms к более современным архитектурным решениям PHP. Например, как любимая, так и ненавистная система крючков постепенно заменяется. Плагины и аннотации устраняют большую часть необходимости в info хуках, а компонент Symfony Event Dispatcher заменяет некоторые из вызываемых хуков. Хотя они остаются сильными в Drupal 8, вполне возможно, что с Drupal 9 (или, может быть, 10) крючки будут полностью удалены .

В этой статье мы в первую очередь рассмотрим, как работает компонент Symfony Event Dispatcher в Drupal. Кроме того, мы также увидим, как вызывать и реализовывать хук в Drupal 8 для достижения тех же целей, что и в первом.

Чтобы следовать или быстро начать, вы можете найти весь код, с которым мы работаем, здесь, в этом репозитории . Вы можете просто установить модуль, и вы готовы к работе. Используемая версия Drupal 8 является первой версией BETA, поэтому предпочтительно использовать ее для обеспечения совместимости. Альфа 15 тоже должна нормально работать. Давайте погрузимся в.

Что такое компонент Диспетчер событий?

Очень хорошее определение компонента Event Dispatcher можно найти на веб-сайте Symfony :

Компонент EventDispatcher предоставляет инструменты, которые позволяют компонентам приложения взаимодействовать друг с другом, отправляя события и прослушивая их.

Я рекомендую прочитать эту документацию, чтобы лучше понять принципы работы диспетчера событий. Вы получите хорошее представление о том, как это работает в Symfony, поэтому мы не будем здесь это освещать. Скорее, мы увидим пример того, как вы можете использовать его в Drupal 8.

Drupal 8 и диспетчер событий

Для лучшей части этой статьи мы сосредоточимся на демонстрации использования диспетчера событий в Drupal 8. Для этого мы создадим простой демонстрационный модуль ( event_dispatcher_demo ), который имеет форму конфигурации, которая сохраняет два значения в качестве конфигурации. После сохранения этой формы мы отправим событие, которое содержит объект конфигурации и которое позволит другим частям приложения перехватывать и изменять его перед сохранением. Наконец, мы сделаем это, продемонстрировав, как подписаться (или прослушать) эти события.

В Drupal 7 этот тип модульности достигается только с помощью хуков. Крюки вызываются, и модули имеют возможность реализовывать их и вносить свои собственные данные. В конце этой статьи мы увидим, как это сделать и в Drupal 8. Но сначала давайте продолжим работу с нашим демонстрационным модулем.

Если вы не знакомы с основами разработки модулей для Drupal 8, я рекомендую ознакомиться с моими предыдущими статьями этой серии.

Форма

Первое, что нам нужно, это простая форма конфигурации с двумя полями. В файле с именем DemoForm.php расположенном в папке src/Form , у нас есть следующее:

 <?php /** * @file * Contains Drupal\event_dispatcher_demo\Form\DemoForm. */ namespace Drupal\event_dispatcher_demo\Form; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; class DemoForm extends ConfigFormBase { /** * {@inheritdoc} */ public function getFormID() { return 'demo_form'; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->config('event_dispatcher_demo.demo_form_config'); $form['my_name'] = [ '#type' => 'textfield', '#title' => $this->t('My name'), '#default_value' => $config->get('my_name'), ]; $form['my_website'] = [ '#type' => 'textfield', '#title' => $this->t('My website'), '#default_value' => $config->get('my_website'), ]; return parent::buildForm($form, $form_state); } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); $config = $this->config('event_dispatcher_demo.demo_form_config'); $config->set('my_name', $form_state->getValue('my_name')) ->set('my_website', $form_state->getValue('my_website')); $config->save(); } } 

Давайте также создадим для него маршрут (в файле event_dispatcher_demo.routing.yml ), чтобы мы могли получить доступ к форме в браузере:

 event_dispatcher_demo.demo_form: path: 'demo-form' defaults: _form: '\Drupal\event_dispatcher_demo\Form\DemoForm' _title: 'Demo form' requirements: _permission: 'access administration pages' по event_dispatcher_demo.demo_form: path: 'demo-form' defaults: _form: '\Drupal\event_dispatcher_demo\Form\DemoForm' _title: 'Demo form' requirements: _permission: 'access administration pages' 

Так что теперь, если вы указываете свой браузер на example.com/demo-form , вы должны увидеть форму. Отправив его, вы создадите и сохраните объект конфигурации с именем event_dispatcher_demo.demo_form_config который содержит два поля: my_name и my_website .

Диспетчер событий

Теперь пришло время поработать с обработчиком formSubmit() методом formSubmit() ) и отправить событие при сохранении формы. Вот как будет выглядеть новый метод:

 public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); $config = $this->config('event_dispatcher_demo.demo_form_config'); $config->set('my_name', $form_state->getValue('my_name')) ->set('my_website', $form_state->getValue('my_website')); $dispatcher = \Drupal::service('event_dispatcher'); $e = new DemoEvent($config); $event = $dispatcher->dispatch('demo_form.save', $e); $newData = $event->getConfig()->get(); $config->merge($newData); $config->save(); } 

Так что здесь происходит? После того, как мы берем переданные значения и добавляем их в объект конфигурации, как и раньше, мы получаем объект диспетчера событий из контейнера службы:

 $dispatcher = \Drupal::service('event_dispatcher'); 

Помните, что рекомендуется внедрить эту услугу в свой класс, но для краткости мы будем извлекать ее статически. Вы можете прочитать эту статью о внедрении зависимостей и сервисном контейнере для получения дополнительной информации.

Затем мы создаем новый объект DemoEvent и передаем ему $config через его конструктор (мы еще не создали класс DemoEvent , мы сделаем это через минуту). Далее мы используем диспетчер для отправки события нашего типа и присваиваем этому действию идентификатор demo_form.save . Это будет использоваться при подписке на события (мы увидим это позже). Метод dispatch() возвращает объект события с внесенными в него изменениями, чтобы мы могли получить значения конфигурации, которые могли или не могли быть изменены в другом месте, и объединить их в нашу исходную конфигурацию. Наконец, мы сохраняем этот объект, как мы делали изначально.

Прежде чем перейти к части подписки на события нашего приложения, давайте создадим класс DemoEvent мы только что DemoEvent . В файле с именем DemoEvent.php расположенном в папке src/ нашего модуля, мы имеем следующее:

 <?php /** * @file * Contains Drupal\event_dispatcher_demo\DemoEvent. */ namespace Drupal\event_dispatcher_demo; use Symfony\Component\EventDispatcher\Event; use Drupal\Core\Config\Config; class DemoEvent extends Event { protected $config; /** * Constructor. * * @param Config $config */ public function __construct(Config $config) { $this->config = $config; } /** * Getter for the config object. * * @return Config */ public function getConfig() { return $this->config; } /** * Setter for the config object. * * @param $config */ public function setConfig($config) { $this->config = $config; } } 

Как вы можете видеть, это простой класс, который расширяет класс Event по умолчанию и который определяет сеттеры и геттеры для объекта конфигурации, который мы будем передавать, используя это событие. И так как мы создали это, давайте также удостоверимся, что мы use это в файле, где мы определили форму:

 use Drupal\event_dispatcher_demo\DemoEvent; 

Подписчик события

Теперь, когда наша форма функционирует нормально и при сохранении формы отправляется событие, мы должны воспользоваться этим и подписаться на него. Давайте начнем с класса подписчика событий, который реализует EventSubscriberInterface . Внутри файла с именем ConfigSubscriber.php (имя по вашему выбору), расположенного в папке src/EventSubscriber/ , у нас есть следующее:

 <?php /** * @file * Contains Drupal\event_dispatcher_demo\EventSubscriber\ConfigSubscriber. */ namespace Drupal\event_dispatcher_demo\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class ConfigSubscriber implements EventSubscriberInterface { static function getSubscribedEvents() { $events['demo_form.save'][] = array('onConfigSave', 0); return $events; } public function onConfigSave($event) { $config = $event->getConfig(); $name_website = $config->get('my_name') . " / " . $config->get('my_website'); $config->set('my_name_website', $name_website); } } 

Так что здесь происходит? EventSubscriberInterface имеет только один обязательный метод с именем getSubscribedEvents() . Этот метод используется для регистрации событий и обратных вызовов к этим событиям. Итак, выше мы зарегистрировали вызываемую onConfigSave() (найденную в том же классе ниже) для события, отправленного с идентификатором demo_form.save . А в методе обратного вызова мы просто добавляем другое значение к объекту конфигурации (на основе объединения двух существующих значений). Последняя часть предназначена только для наших демонстрационных целей: здесь вы можете делать то, что хотите.

Когда мы подписались на метод onConfigSave() для прослушивания события demo_form.save , мы передали весовой коэффициент 0. Если вы регистрируете несколько обратных вызовов для одного и того же события, этот вес становится важным (чем больше число, тем раньше он вызывается) , И если обратный вызов изменяет те же значения, что и ранее, они будут переопределены. Это хорошо иметь в виду.

Теперь, чтобы этот подписчик событий работал, нам нужно определить его как сервис и присвоить ему тег event_subscriber . Таким образом, в файле с именем event_dispatcher_demo.services.yml находится в корневой папке нашего модуля, мы получим следующее:

 services: event_dispatcher_demo.config_subscriber: class: Drupal\event_dispatcher_demo\EventSubscriber\ConfigSubscriber tags: - { name: event_subscriber } 

Это простое определение сервиса с правильным тегом, которое заставит контейнер автоматически создавать экземпляр объекта этого класса всякий раз, когда диспетчер находится в игре. И это в значительной степени это. Очистите кеш, и если вы снова сохраните форму, сохраненный объект конфигурации всегда будет содержать новое значение, основанное на первых двух.

Крючки

В заключительной части этой статьи мы продемонстрируем использование хуков для достижения аналогичной цели.

Во-первых, давайте изменим обработчик отправки формы, и вместо того, чтобы отправлять события, мы вызовем ловушку и передадим ей значения конфигурации. Вот как будет выглядеть новый submitForm() :

 public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); $config = $this->config('event_dispatcher_demo.demo_form_config'); $config->set('my_name', $form_state->getValue('my_name')) ->set('my_website', $form_state->getValue('my_website')); $configData = $config->get(); $newData = \Drupal::service('module_handler')->invokeAll('demo_config_save', array($configData)); $config->merge($newData); $config->save(); } 

Мы не используем ни объекты событий, ни диспетчерскую службу. Вместо этого мы получаем службу обработчика модулей, которая содержит метод invokeAll() используемый для вызова реализаций перехвата из всех модулей. По сути, это замена помощника Drupal 7 module_invoke_all () . И снова, рекомендуется внедрить этот сервис, но для краткости мы будем извлекать его статически.

В нашем случае вызывается реализация ловушки — hook_demo_config_save и она получает один параметр — массив значений, извлеченных из нашего объекта конфигурации. Внутри $newData у нас будет массив значений, объединенных из всех реализаций этого хука. Затем мы объединяем это с нашим объектом конфигурации и, наконец, сохраняем его.

Давайте быстро увидим пример реализации ловушки. Как и в Drupal 7, они могут быть только в файлах .module :

 /** * Implements hook_demo_config_save(). */ function event_dispatcher_demo_demo_config_save($configValues) { $configValues['my_name_website'] = $configValues['my_name'] . " / " . $configValues['my_website']; return $configValues; } 

Как вы можете видеть, мы добавляем новое значение в массив конфигурации, которое позже будет объединено с сохранением объекта. По сути, мы имеем то же самое, что и с диспетчером событий.

Вывод

В этой статье мы рассмотрели, как работает компонент Symfony Event Dispatcher в Drupal 8. Мы узнали, насколько гибким оно делает наше приложение, когда оно позволяет другим расширять функциональность. Кроме того, мы видели, как работают вызываемые хуки в новой версии Drupal. С тех пор, как Drupal 7 изменился, мало что изменилось, кроме частоты, с которой они используются. Многие хуки были заменены плагинами и аннотациями, а компонент Event Dispatcher также взял на себя большую долю того, что было в D7 за хуки.

Хотя подход Event Dispatcher более многословен, это рекомендуемый путь для продвижения вперед. Там, где это возможно, мы больше не используем устаревшую процедурную характеристику для хуков, а скорее объектно-ориентированные, не связанные между собой и проверяемые решения. И Symfony очень помогает в этом.