Новый способ реализации хуков в Drupal 11

alexei11/11/2025 - 09:06
Новый способ реализации хуков в Drupal 11

Реализация хуков с использованием PHP атрибутов — это проще, легко тестируется и позволяет внедрять зависимости.

Источник: Nieuwe manier van hooks schrijven in Drupal 11 uitgelegd by Door Teade Geertsma, Emble

С выходом Drupal 11.1 появился новый способ реализации хуков. Ранее хуки всегда писались процедурно, теперь же их можно реализовать с использованием атрибутов в классах PHP. Эта статья объясняет, что конкретно изменилось, какие преимущества это приносит, какими способами это можно применить и как использовать новый подход, сохраняя совместимость с предыдущими версиями.

Преимущества

Реализация хуков в объектно-ориентированном стиле предлагает несколько преимуществ. Ниже приведены ключевые выгоды с краткими пояснениями:

  • Автозагрузка: Больше не сканируется файл .module, методы автоматически обнаруживаются через атрибуты, что улучшает скорость начальной загрузки системы.
  • Внедрение зависимостей: Поскольку хуки теперь объектно-ориентированы, можно внедрять сервисы.
  • Тестируемость: Хуки теперь содержатся в классах, что позволяет проводить их тестирование без запуска ядра Drupal.
  • Организованность: Возможность размещать хуки, добавляемые модулем, в разные классы, обеспечивая лучшее упорядочивание. Например, можно сгруппировать все хуки, связанные с нодами, в один класс.

Одним из основных преимуществ нового подхода объектно-ориентированных хуков в Drupal 11 является работа автозагрузки. Раньше все файлы .module загружались по умолчанию, даже если они содержали всего пару ненужных хуков. Это занимало лишнее время. Сейчас автозагрузка работает умнее: благодаря механизму PSR-4 классы загружаются только тогда, когда они действительно нужны. Чем меньше файлов приходится открывать системе, тем быстрее запускается сайт. Если для отдельного модуля выигрыш кажется незначительным, то владельцы сайтов с множеством модулей вскоре заметят накопленную выгоду.

Реализация атрибута хука

Пример 1

Добавляем библиотеку ко всем формам.


<?php
declare(strict_types=1);
namespace Drupal\custom_module\Hook;
use Drupal\Core\Hook\Attribute\Hook;

/**
 * Хуки модуля для модификации форм.
 */
class CustomModuleFormHooks {
  public function __construct() {}

  #[Hook('form_alter')]
  public function formAlter(&$form, &$form_state, $form_id): void {
    $form['#attached']['library'][] = 'custom_module/form';
  }
}

Пример 2

Добавляем библиотеку к конкретной форме.


<?php
declare(strict_types=1);
namespace Drupal\custom_module\Hook;
use Drupal\Core\Hook\Attribute\Hook;

/**
 * Хуки модуля для модификации форм.
 */
class CustomModuleFormHooks {
  public function __construct() {}

  #[Hook('form_node_article_form_alter')]
  public function formAlter(&$form, &$form_state, $form_id): void {
    $form['#attached']['library'][] = 'custom_module/form';
  }
}

Пример 3

Добавляем библиотеку к двум разным формам.


<?php
declare(strict_types=1);
namespace Drupal\custom_module\Hook;
use Drupal\Core\Hook\Attribute\Hook;

/**
 * Хуки модуля для модификации форм.
 */
class CustomModuleFormHooks {
  public function __construct() {}

  #[Hook('form_node_article_form_alter')]
  #[Hook('form_node_article_edit_form_alter')]
  public function formAlter(&$form, &$form_state, $form_id): void {
    $form['#attached']['library'][] = 'custom_module/form';
  }
}

Другие подходы

Также можно разместить атрибут хука на самом классе, задав имя метода в параметре атрибута хука:

Пример


<?php
declare(strict_types=1);
namespace Drupal\custom_module\Hook;
use Drupal\Core\Hook\Attribute\Hook;

/**
 * Хуки модуля для модификации форм.
 */
#[Hook('form_alter', method: 'formAlter')]
class CustomModuleFormHooks {
  public function __construct() {}

  public function formAlter(&$form, &$form_state, $form_id): void {
    $form['#attached']['library'][] = 'custom_module/form';
  }
}

Если вы используете магический метод __invoke, то имя метода можно опустить:

Пример


<?php
declare(strict_types=1);
namespace Drupal\custom_module\Hook;
use Drupal\Core\Hook\Attribute\Hook;

/**
 * Хуки модуля для модификации форм.
 */
#[Hook('form_alter')]
class CustomModuleFormHooks {
  public function __construct() {}

  public function __invoke(&$form, &$form_state, $form_id): void {
    $form['#attached']['library'][] = 'custom_module/form';
  }
}

Порядок выполнения хуков

Начиная с Drupal 11.2, хуки также получили аргумент порядка order. Это позволяет управлять очередью выполнения реализаций хуков:

Пример


// Выполнить хук первым.
#[Hook('some_hook', order: Order::First)]

// Выполнить хук последним.
#[Hook('some_hook', order: Order::Last)]

// Выполнить хук раньше реализации другого модуля.
#[Hook('some_hook', order: new OrderBefore(['other_module']))]

// Выполнить хук после реализации другого модуля.
#[Hook('some_hook', order: new OrderAfter(['other_module']))]

Обратная совместимость

Если вы хотите воспользоваться новым способом реализации хуков и поддержать совместимость с Drupal 10.1 и 11.0, рекомендуется зарегистрировать новый класс хуков вручную как сервис. Затем можно добавить процедуру-обёртку для поддержки старого способа.

Предположим, вы написали хук в новом стиле:

Пример


<?php
namespace Drupal\custom_module\Hook;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Entity\EntityInterface;

class CustomModuleHooks {
  #[Hook('node_create')]
  public function nodeCreate(EntityInterface $entity) {
    // Ваш кастомный код.
  }
}

Зарегистрируйте его как сервис:

Пример


services:
  Drupal\custom_module\Hook\CustomModuleHooks:
    class: Drupal\custom_module\Hook\CustomModuleHooks
    autowire: true

Затем создайте процедурную оболочку:

Пример


<?php
use Drupal\Core\Hook\Attribute\LegacyHook;
use Drupal\custom_module\Hook\NodeHooks;
use Drupal\Core\Entity\EntityInterface;

#[LegacyHook]
function custom_module_node_create(EntityInterface $entity) {
  return \Drupal::service(NodeHooks::class)->nodeCreate($entity);
}

Предпроцессорные хуки

Начиная с Drupal 11.2 (или хотя бы с 11.1.8), предпроцессорные хуки также поддерживаются в новом объектно-ориентированном стиле. Однако это применимо только к модулям; предпроцессорные хуки в темах остаются процедурными.

Хуки, остающиеся процедурными

Некоторые типы хуков продолжают работать только в процедурном стиле:

  • hook_hook_info()
  • hook_module_implements_alter()
  • hook_install()
  • hook_install_tasks()
  • hook_install_tasks_alter()
  • hook_post_update_NAME()
  • hook_requirements()
  • hook_schema()
  • hook_uninstall()
  • hook_update_last_removed()
  • hook_update_N()

Полезно знать

Старый процедурный способ реализации хуков продолжает работать. Drupal 11.1 и новее поддерживает оба подхода параллельно. Мы рекомендуем использовать новый объектно-ориентированный подход для нового кода, но старые модули не требуют немедленной переработки.

В ядро Drupal была встроена служба сбора хуков. Во время построения контейнера система ищет все службы с атрибутом Hook. Разработчику достаточно поместить класс в правильный каталог и пространство имён и включить параметр autowire: true. Всё остальное сделает сам Drupal.

Существует возможность наличия нескольких классов в одном модуле, реализующих один и тот же хук, или влиять на порядок выполнения с помощью нового параметра order. Начиная с Drupal 11.2, предпроцессорные хуки также можно использовать в новом стиле, но только в модулях.

Что это значит для будущего?

Этот шаг идеально согласуется с направлением развития Drupal: современный, объектно-ориентированный и дружелюбный к разработчикам. Атрибуты и внедрение сервисов делают код чище, тестируемее и приятнее в разработке.