
Реализация хуков с использованием 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: современный, объектно-ориентированный и дружелюбный к разработчикам. Атрибуты и внедрение сервисов делают код чище, тестируемее и приятнее в разработке.