Хуки и события в Drupal: как настроить без ошибок
За 20–30 минут вы создадите небольшой модуль для Drupal 10/11: хук будет записывать создание материалов в журнал, а подписчик события — добавлять заголовок к HTTP-ответу.

За 20–30 минут вы создадите небольшой модуль для Drupal 10/11: хук будет записывать создание материалов в журнал, а подписчик события — добавлять заголовок к HTTP-ответу. На этом примере видно главное различие: хук привязан к механике Drupal, а событие — к общему жизненному циклу приложения.
Что понадобится до начала
- Drupal 10 или Drupal 11 с доступом к файловой системе сайта;
- права администратора в Drupal;
- доступ к журналу сообщений на странице «Отчёты» → «Последние сообщения журнала»;
- текстовый редактор с сохранением файлов в UTF-8;
- если используете Composer или Drush — их рабочая установка, но для примера они не обязательны.
Пример рассчитан на стандартный docroot web. Если Drupal установлен в корне проекта, замените web на корневую папку сайта. Модуль назовём site_audit: имя из нижнего регистра с подчёркиваниями должно совпадать в имени папки, файлов и PHP-функций.
1 Событие — сигнал о том, что в приложении произошёл определённый этап или действие. На него можно подписать отдельный сервис, не помещая логику в процедурный файл модуля.
Правильный первый вопрос не «какой API вызвать», а «кто владеет моментом»: Drupal-хук или Symfony-событие.
Шаг 1. Создайте каркас модуля
В папке web/modules/custom создайте каталог site_audit. В нём понадобятся файл описания модуля, процедурный файл с хуком, YAML-файл сервисов и класс подписчика:
web/modules/custom/site_audit/
├── site_audit.info.yml
├── site_audit.module
├── site_audit.services.yml
└── src/
└── EventSubscriber/
└── ResponseHeaderSubscriber.php
Почему так: Drupal ищет описание модуля в файле .info.yml, а классы с пространством имён — в каталоге src по правилам PSR-4.
Проверка. После создания файлов путь к site_audit.info.yml должен выглядеть как web/modules/custom/site_audit/site_audit.info.yml, а регистр букв в имени каталога и класса должен совпадать.
Шаг 2. Опишите и включите модуль
Создайте site_audit.info.yml:
name: Site audit
type: module
description: 'Пример хуков и событий для Drupal.'
package: Custom
core_version_requirement: ^10 || ^11
В YAML перед type и остальными ключами не должно быть лишних пробелов. В примере выше отступы показаны без начальных пробелов у ключей, поэтому файл должен содержать именно такой текст:
name: Site audit
type: module
description: 'Пример хуков и событий для Drupal.'
package: Custom
core_version_requirement: ^10 || ^11
В административной панели откройте «Расширение», найдите Site audit и нажмите «Установить». Если установлен Drush, после копирования файлов можно выполнить drush en site_audit -y. Команда не входит в обязательную часть примера.
Проверка. Модуль появляется в списке расширений без сообщения о синтаксической ошибке YAML и получает статус «Включён».
Шаг 3. Добавьте хук создания материала
Откройте site_audit.module и вставьте полный код:
<?php
use Drupal\node\NodeInterface;
/**
* Записывает создание материала в журнал Drupal.
*/
function site_audit_node_insert(NodeInterface $node): void {
\Drupal::logger('site_audit')->notice(
'Создан материал «@title» с идентификатором @id.',
[
'@title' => $node->label(),
'@id' => $node->id(),
]
);
}
Имя функции строится из машинного имени модуля и имени хука: site_audit плюс node_insert. Этот хук срабатывает после создания сущности материала, но не при каждом редактировании уже существующего материала.
Проверка. Создайте новый материал, сохраните его, затем откройте «Отчёты» → «Последние сообщения журнала». В списке должна появиться запись с заголовком и ID материала.
Шаг 4. Подпишите сервис на событие ответа
Теперь добавим класс, который будет устанавливать HTTP-заголовок для ответа Drupal. Это безопасный демонстрационный эффект: его можно увидеть в инструментах разработчика браузера или через клиент, который показывает заголовки ответа.
Файл site_audit.services.yml:
services:
site_audit.response_subscriber:
class: Drupal\site_audit\EventSubscriber\ResponseHeaderSubscriber
tags:
- { name: event_subscriber }
Файл src/EventSubscriber/ResponseHeaderSubscriber.php:
<?php
namespace Drupal\site_audit\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
final class ResponseHeaderSubscriber implements EventSubscriberInterface {
/**
* Возвращает события, на которые подписан класс.
*/
public static function getSubscribedEvents(): array {
return [
KernelEvents::RESPONSE => 'onResponse',
];
}
/**
* Добавляет диагностический заголовок к HTTP-ответу.
*/
public function onResponse(ResponseEvent $event): void {
$event->getResponse()->headers->set('X-Site-Audit', 'enabled');
}
}
Сервисный контейнер Drupal найдёт класс по пространству имён, а тег event_subscriber зарегистрирует его как подписчика Symfony-событий. После добавления YAML Drupal нужно перестроить кэш.
Проверка. Очистите кэш через «Конфигурация» → «Разработка» → «Производительность» → «Очистить кэш». Откройте страницу сайта, в сетевых инструментах найдите ответ и заголовок X-Site-Audit: enabled.
Шаг 5. Проверьте границы хуков и событий
Хук и подписчик живут в разных слоях. Процедурный хук удобен, когда Drupal явно предоставляет точку расширения для сущности или модуля. Событие подходит для этапов Symfony и Drupal Kernel: запроса, ответа, исключения и других системных процессов.
- Создайте новый материал. Проверьте одну запись в журнале.
- Измените этот материал. Новая запись от
site_audit_node_insertпоявиться не должна: для обновления используется другой хук. - Откройте несколько страниц сайта. Проверьте заголовок ответа на каждой странице.
- Отключите модуль, очистите кэш и снова откройте страницу. Заголовок должен исчезнуть, а хук перестать записывать новые сообщения.
Проверка. После последнего пункта у вас нет старого заголовка в новом ответе и нет новых журналов от отключённого модуля. Уже сохранённые сообщения журнала при этом могут остаться — отключение модуля не обязано удалять историю.
Если не получилось
Модуль не появляется в списке
Проверьте расположение site_audit.info.yml, имя папки и значение type: module. Частая причина — файл назван site_audit.info.yaml или сохранён внутри дополнительного каталога.
Drupal сообщает об ошибке YAML
Проверьте отступы в site_audit.services.yml. В YAML используются пробелы, а не табуляция. Убедитесь, что после services: ключ сервиса отступает на два пробела, а class и tags — ещё на два.
Появилась ошибка «Class ... not found»
Сверьте путь и пространство имён: файл должен лежать в src/EventSubscriber/ResponseHeaderSubscriber.php, а класс — начинаться с namespace Drupal\site_audit\EventSubscriber;. Проверьте регистр букв: Linux различает EventSubscriber и eventsubscriber.
Хук не срабатывает при редактировании
Это ожидаемое поведение. hook_node_insert() относится к созданию, а не к обновлению. Если нужно реагировать на сохранение существующего материала, изучите hook_node_update(), но не заменяйте один хук другим без проверки бизнес-логики.
Заголовок не виден в браузере
Очистите кэш Drupal и проверьте именно сетевой ответ, а не HTML-код страницы. Если перед Drupal стоит CDN, прокси или кэш веб-сервера, он может отдавать сохранённый ответ; сначала проверьте запрос к origin-окружению.
Что делать дальше
Удалите диагностический заголовок перед публикацией, если он не нужен приложению. Затем добавьте тесты на два сценария: создание материала должно писать журнал, а обычный HTTP-ответ — содержать заголовок только при включённом модуле. Для рабочих действий не оставляйте тяжёлую логику внутри хука: вынесите её в сервис и вызывайте сервис из хука или подписчика. Следующим показателем для наблюдения будет количество сообщений канала site_audit и время обработки запроса после подключения настоящей логики.
Архитектор и дизайнер интерьера: Законные перепланировки (БТИ), расчёт плитки, обоев, материалов.
Спросить за 15 ₽Источники: Creating custom modules — Drupal.org, Events — Drupal.org
Частые вопросы
Чем хук в Drupal отличается от события?
Почему после изменения services.yml ничего не изменилось?
Можно ли использовать этот пример в Drupal 9?
Что делать, если хук нужен и при создании, и при изменении материала?
Нужно ли очищать кэш после изменения PHP-файла модуля?
Почему событие может выполняться несколько раз за один запрос?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.