Евгений Никитин Евгений Никитин

Русский English

Альтернативы системе хуков в Drupal 10

Для изменения поведения кода в Drupal была реализована система хуков. По своей сути она является реализацией шаблона проектирования “Посредник” в процедурном программировании и предоставляет единый интерфейс для “общения” разных частей системы.

Время не стоит на месте и на смену процедурному подходу в Drupal версиях до 8 пришел объектно-ориентированный. Drupal 8 построен на основе фреймворка Symfony в котором уже есть реализация шаблона “Посредник” в библиотеке symfony/event-dispatcher. Таким образом в ядре Drupal существуют две параллельные системы предоставляющие возможность для коммуникации компонентов друг с другом - хуки и события.

Почему же в данное время существуют две, по-сути дублирующие системы, и какие есть альтернативы?

Хуки

Как уже было сказано хуки достались Drupal 10 в наследство от более старых версий. Они являлись отличительной особенностью Drupal и когда была миграция на Symfony, то их не стали полностью вырезать и оставили как часть идентичности Drupal, которая была привычна и понятна программистам. Для того, чтобы использовать в процедурном коде такой мощный инструмент Symfony как сервисы и внедрение зависимостей, пришлось создавать класс \Drupal - оболочку статичного контейнера сервисов. По-сути, нам нужен класс \Drupal только ради хуков и препроцессов шаблонов потому, что в классах вместо \Drupal мы используем внедрение зависимостей для получения сервисов. Таким образом, поддерживая хуки нам нужно поддерживать дополнительный функционал для возможности использования в хуках сервисов, что усложняет ядро Drupal.

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

События и Hook Event Dispatcher

В данное время в Drupal сложилась странная ситуация, что для расширения определенного функционала нам нужно использовать хуки (например hook_form_alter() для изменения работы формы), а для другого - события (например изменение существующего пути). То, что основано на компонентах Symfony изменяется через события, а то, что реализовано в Drupal - через хуки. Довольно таки неудобно, не правда ли?

Попытка избавиться от хуков была предпринята еще в Drupal 8, затем это отложили до Drupal 9, но в Drupal 10 они все также присутствуют. Ядро Drupal не предоставляет события которые мы бы могли использовать для замены хуков. Но к счастью есть модуль Hook Event Dispatcher, который предоставляет события аналогичные хукам.

Для изменения формы поиска через хуки нам достаточно кода:

/**
 * Implements hook_form_FORM_ID_alter().
 */
function example_form_search_block_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  $form['keys']['#attributes']['placeholder'] = t('Search');
}

Для использования события из модуля “Hook Event Dispatcher” нам нужно вначале определить подписчик:

services:
 example.form_subscribers:
   class: Drupal\example\ExampleFormEventSubscribers
   tags:
     - { name: event_subscriber }

А затем реализовать его:

class ExampleFormEventSubscribers implements EventSubscriberInterface {

  /**
   * Alter search form.
   *
   * @param \Drupal\core_event_dispatcher\Event\Form\FormIdAlterEvent $event
   *   The event.
   */
  public function alterSearchForm(FormIdAlterEvent $event): void {
    $form = &$event->getForm();
    // Add placeholder.
    $form['keys']['#attributes']['placeholder'] = $this->t('Search');
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      'hook_event_dispatcher.form_search_block_form.alter' => 'alterSearchForm',
    ];
  }
}

Также “Hook Event Dispatcher” может использоваться для темизации шаблонов - он предоставляет preprocess события для шаблонов ядра.

Если вы используете в своем проекте какой-то модуль, который определяет свои хуки или шаблоны, то вам придется реализовать события для этого хука в своем проекте самостоятельно, что, конечно, не сделает вашу жизнь проще. Также наглядно видно насколько меньше кода нужно для хука чем для события.

Но у событий есть преимущества над хуками:

  • Проще определять последовательность выполнения событий.
  • События могут блокировать выполнение последующих событий.
  • Возможность определять слушателей динамически.

Hux

Hux это модуль, который предоставляет возможность сочетать внедрение зависимостей и ООП с простотой использования.

Пример выше с изменением формы поиска в Hux будет выглядеть вот так:

<?php

namespace Drupal\example\Hooks;

/**
* Examples.
*/
final class ExampleHooks {

  #[Alter('form_system_site_information_settings')]
  public function formSystemSiteInformationSettingsAlter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
    $form['keys']['#attributes']['placeholder'] = t('Search');
  }
}

Как вы видите все выглядит довольно просто - файл будет найден автоматически если будет размещен в example/src/Hooks папке. Для определения хука используется PHP аннотация. Если класс реализует ContainerInjectionInterface, то можно подключить любые сервисы через внедрение зависимостей.

Hux не умеет работать с шаблонами. Так что preprocess функции придется определять как обычно. Но вы можете использовать любые хуки ядра и модулей. Также есть поддержка весов для изменения порядка выполнения хуков и возможность заменять реализацию хуков в модулях на свою реализацию.

Что в итоге?

Основная проблема “Hook Event Dispatcher” и “Hux” это то, что они декорируют стандартный сервис module_handler. Ядро Drupal как обычно вызывает хуки, а этим модулям приходится поддерживать как стандартную реализацию хуков, так и свою собственную, добавляя сложность и не делая всю систему быстрее. И если события из “Hook Event Dispatcher” вы можете вызвать используя Event Dispatcher, то для Hux вам все равно придется вызывать хуки. Если завтра от хуков откажутся, то Hux окажется бесполезным. Хотя, в данный момент пользоваться ими удобнее чем событиями.

Для себя я решил, что при разработке новых модулей, в случае когда нужна будет возможность расширения функционала, я буду реализовывать события. В любом случае от событий мы никуда уже не денемся и в долгосрочной перспективе проще будет поддерживать именно их, чем переходить с хуков на события. Но для существующих хуков в Drupal 10 я бы рекомендовал использовать стандартный функционал, без установки “Hook Event Dispatcher” или “Hux” с их накладными расходами и дополнительным усложнением. По-сути эти модули являются попыткой исправить архитектуру Drupal. Но чтобы решить проблему хуков эффективно нужно сделать это именно в ядре.

Ссылки: