Обработчик события - регистрация, аргументы, отмена действия
Подписываемся на событие модуля, разбираемся с аргументами и отменяем действие до того, как оно выполнилось.
Решение
Регистрируем обработчик на текущий запрос:
// /local/php_interface/init.php - подключается на каждом запросе к сайту// имя модуля и имя события пишут ровно так, как они заданы в коде ядраuse Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler( 'iblock', 'OnBeforeIBlockElementAdd', static function (&$fields) { // сюда попадаем до создания элемента: поля ещё можно изменить $fields['NAME'] = trim($fields['NAME']); // объекты ядра здесь уже созданы, в отличие от самого init.php });Такой обработчик живёт ровно один запрос и подключается заново на каждом хите сайта. Для проектного кода этого достаточно, а модулю нужна постоянная регистрация: она хранится в базе и снимается вместе с самим модулем.
Отменяем действие из события «до»:
EventManager::getInstance()->addEventHandler( 'iblock', 'OnBeforeIBlockElementAdd', static function (&$fields) { if (empty($fields['CODE'])) { global $APPLICATION; $APPLICATION->throwException('символьный код обязателен'); return false; // false отменяет создание элемента } });Отменить можно только в событии «до». Возврат отрицательного результата останавливает действие, а текст исключения попадает в сообщение об ошибке интерфейса.
Читаем результат в событии «после»:
EventManager::getInstance()->addEventHandler( 'iblock', 'OnAfterIBlockElementAdd', static function (&$fields) { if ($fields['RESULT']) { // идентификатор созданного элемента file_put_contents('/tmp/iblock.log', date('c') . " создан {$fields['RESULT']}\n", FILE_APPEND); } });В событии «после» действие уже выполнено, и отменять там попросту нечего. Зато здесь уместны уведомления, запись в журнал и обновление связанных с записью данных.
Проверяем, что обработчик вообще подключился:
$handlers = EventManager::getInstance() ->findEventHandlers('iblock', 'OnBeforeIBlockElementAdd');printf("обработчиков: %d\n", count($handlers));foreach ($handlers as $handler) { // видно и постоянные из базы, и добавленные в init.php на этот хит printf(" %s\n", $handler['CALLBACK'][1] ?? 'замыкание');}Пустой список означает, что файл инициализации не подключается или регистрация не выполняется. Ноль обработчиков объясняет молчание лучше, чем поиск ошибки внутри самой функции.
Обработчики удобно держать отдельными файлами, а в файле инициализации только подключать их. Иначе через год этот файл превращается в тысячу строк несвязанной логики, где обработчик каталога соседствует с обработчиком авторизации.
События вызывает код модуля, а не сама база данных. Элемент, созданный прямым запросом к таблице или чужим кодом мимо API, не породит ни одного события. Поэтому обработчик, который «не срабатывает при импорте», обычно упирается в импорт, а не в подписку.
Порядок обработчиков одного события задаётся порядком их регистрации. Несколько обработчиков на одно событие выполняются по очереди, и первый же отменивший действие останавливает всю цепочку: следующие не вызываются вовсе. Это стоит помнить, когда на одном проекте живут и обработчики модуля из маркетплейса, и свои.
Типичные проблемы
Обработчик не срабатывает вовсе.
Действие идёт мимо API модуля - прямым запросом к базе или сторонним кодом. События вызывает модуль, а не хранилище данных.
Отмена не работает.
Обработчик подписан на событие «после». В нём действие уже выполнено, и отменять нечего.
Изменённые поля не сохранились.
Аргумент принят без ссылки. Событие «до» передаёт поля по ссылке, и без неё правки остаются в локальной копии.
Обработчик пропал после обновления модуля.
Он регистрировался в файлах модуля. Свой код держат в каталоге локальных расширений, который обновление не трогает.
Часть обработчиков не выполняется.
Один из предыдущих отменил действие. Цепочка обработчиков останавливается на первой отмене.
Частые вопросы
Чем разовая регистрация отличается от постоянной?
Разовая действует на текущий запрос и живёт в файле инициализации. Постоянная хранится в базе, ставится при установке модуля и снимается при его удалении.
Как узнать имя нужного события?
По документации модуля и по его коду: события вызываются явно и находятся поиском. Единого списка всех событий платформы нет.
Можно ли подписаться на событие чужого модуля?
Да, для этого события и существуют. Свой код при этом кладут в каталог локальных расширений, а не внутрь чужого модуля.
Почему обработчик выполняется дважды?
Он зарегистрирован и постоянно, и разово. Проверяют список обработчиков события: дубль виден сразу по двум одинаковым записям.
Смежное
- Как найти нужное событие: поиск по ядру и три системы - с чего начинают, когда имя события неизвестно
- События на практике - оглавление подтемы
- Файл инициализации: порядок подключения, что доступно, ошибки - где регистрируют обработчики проекта
- Легаси-код в проекте: как читать, править и переводить на D7 - старые события и их регистрация
- Обработчик меняет данные: рекурсия, флаг, массовые операции - когда обработчик сам пишет в базу
- Обработчик события не срабатывает: разбор причин - когда обработчик молчит
- Журнал событий: чтение, очистка, свои записи - куда писать результат
- Call to a member function on null: причины по убыванию частоты - куда переносить слишком ранний код
- Ядро D7 - основы - устройство событийной модели
- События, агенты и бизнес-процессы - соседние механизмы расширения
- Свой агент и задание по расписанию: создание, шаг, защита - где живёт код фоновых задач
- События оформления заказа: свои проверки, запрет и допданные - события заказа на практике
- Свой модуль: структура, установка, автозагрузка - где регистрируют обработчики решения
- Своя логика при обмене: обработчики, защита правок, журнал - обработчики на сеансе обмена
- Свои события в своём коде: как дать другим точку расширения - обратная сторона: своё событие
- Готовое решение в проекте: установка, вмешательство, удаление - как найти чужие подписки