События ядра Битрикс: где регистрировать обработчики
Обработчики событий ядра Битрикс можно повесить в init.php или зарегистрировать в собственном модуле. Разница проявляется на деплое, при отладке и в тот день, когда обработчик уходит в рекурсию.

Обработчик события в Битриксе можно повесить тремя способами, и на разработке они работают одинаково. Разница проявляется позже: init.php перезаписали старой версией при деплое, обработчик продублировался и письма клиентам уходят дважды, или OnAfterIBlockElementUpdate вызвал сам себя, пока PHP не упал. Ниже о том, где держать обработчики и что ломается первым. Код рассчитан на «1С-Битрикс: Управление сайтом» с D7 (main 22.x и новее) и PHP 8.x. Сигнатуры конкретных событий сверяйте с документацией вашей версии.
Три места для обработчика
Событие в ядре - это точка, где вызываются подписанные функции. Подписку можно держать в коде, который выполняется на каждом хите, или в базе данных.
| Подход | Где регистрация | Выигрыш | Цена |
|---|---|---|---|
| Функции в init.php | /local/php_interface/init.php | Пишется за минуту | Файл разрастается, имена функций конфликтуют |
| Классы в /local, подписка в init.php | init.php, логика в классах | Автозагрузка, код читается и тестируется | Автозагрузку нужно регистрировать вручную |
| Свой модуль | Таблица b_module_to_module | Ставится и снимается из админки, не зависит от init.php | Нужен install/index.php и обязательная отмена подписки при удалении |
Пояснение. D7 - ядро Битрикса второго поколения: пространства имён вроде Bitrix\Main, ORM, объект Event. Старое ядро - классы с префиксом C, например CIBlockElement. Оба живут в одном проекте.
Подписка через init.php
Рабочий вариант для одного проекта: init.php только подписывает, логика лежит в классе. Пока событие не сработало, класс не загружается.
<?php
// /local/php_interface/init.php
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
// Пути к классам считаются от корня сайта
Loader::registerAutoLoadClasses(null, [
'Local\Events\IblockHandler' => '/local/php_interface/lib/events/iblockhandler.php',
]);
$eventManager = EventManager::getInstance();
$eventManager->addEventHandler('iblock', 'OnBeforeIBlockElementAdd', ['Local\Events\IblockHandler', 'onBeforeElementAdd']);
$eventManager->addEventHandler('iblock', 'OnAfterIBlockElementUpdate', ['Local\Events\IblockHandler', 'onAfterElementUpdate']);
Подписка через модуль
Запись о подписке хранится в базе, поэтому переживает перезапись init.php. Когда событие срабатывает, ядро само подключает указанный модуль. Класс лежит в lib/ модуля и подхватывается автозагрузкой ядра.
// Схематично: фрагменты методов класса модуля vendor.events
use Bitrix\Main\EventManager;
// DoInstall(): после RegisterModule
EventManager::getInstance()->registerEventHandler(
'iblock', 'OnBeforeIBlockElementAdd',
$this->MODULE_ID, '\Vendor\Events\IblockHandler', 'onBeforeElementAdd'
);
// DoUninstall(): те же пять параметров
EventManager::getInstance()->unRegisterEventHandler(
'iblock', 'OnBeforeIBlockElementAdd',
$this->MODULE_ID, '\Vendor\Events\IblockHandler', 'onBeforeElementAdd'
);
Если модуль удалили, а запись осталась, в момент события будет фатальная ошибка: класса нет. Поэтому отмена подписки при удалении обязательна.
Что ломается
Старые и D7-события
Старые события вроде OnBeforeIBlockElementAdd передают поля массивом по ссылке: вы правите $fields прямо в обработчике. D7-события, например OnSaleOrderSaved в модуле sale, передают объект Event, данные берут через getParameter('ENTITY') и getParameter('IS_NEW'). Как устроено конкретное событие, смотрите в его описании, а не по аналогии с соседним.
Типичные ошибки
- Рекурсия. Update или Add внутри After-обработчика снова вызывает то же событие. Нужен флаг, как в коде ниже.
- Нет фильтра по инфоблоку. Событие iblock срабатывает для всех инфоблоков сразу, первой строкой проверяйте $fields['IBLOCK_ID'].
- Дубли. Обработчик, подписанный и в init.php, и в модуле, отработает дважды.
- Тяжёлая работа внутри. HTTP-запрос к внешнему сервису в синхронном обработчике тормозит сохранение в админке и импорт.
- Глобальные события вроде OnEndBufferContent и OnProlog идут на каждой странице, включая админку. Проверяйте константу ADMIN_SECTION и не гоняйте по буферу лишние регулярки.
Отмена операции и защита от рекурсии в одном классе:
<?php
// /local/php_interface/lib/events/iblockhandler.php
namespace Local\Events;
class IblockHandler
{
private static bool $inProgress = false;
// Отмена добавления: возвращаем false и кладём текст ошибки
public static function onBeforeElementAdd(array &$fields): bool
{
global $APPLICATION;
if (trim((string)($fields['NAME'] ?? '')) === '') {
$APPLICATION->ThrowException('Укажите название элемента');
return false;
}
return true;
}
// Update внутри обработчика снова вызовет событие, поэтому флаг
public static function onAfterElementUpdate(array &$fields): void
{
if (self::$inProgress || empty($fields['RESULT'])) {
return;
}
self::$inProgress = true;
try {
(new \CIBlockElement())->Update((int)$fields['ID'], ['SORT' => 500]);
} finally {
self::$inProgress = false;
}
}
}
Если в init.php появилась логика, это уже не конфиг, а место, где заводятся баги. Пусть он только подписывает.
Когда что выбирать
- Один сайт, несколько обработчиков, один разработчик: классы в /local и подписка в init.php.
- Больше пяти обработчиков, общая логика на нескольких сайтах или нужно включать и выключать её из админки: модуль.
- Функции прямо в init.php: только как временный хотфикс, который вы запишете в задачи на вынос.
Что делать дальше
- Найдите по проекту все вызовы addEventHandler и AddEventHandler и сверьте их с записями в b_module_to_module: ищите дубли и классы, которых уже нет.
- Замерьте время сохранения элемента в админке с включёнными обработчиками и без них. Всё, что добавляет заметную долю, выносите из синхронного пути.
- Тяжёлую работу переносите в агенты или очередь. Это отдельная тема, здесь она не разбирается.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Документация D7 (1С-Битрикс), Справочник API ядра (1С-Битрикс)
Частые вопросы
Обработчик не срабатывает. Что проверить в первую очередь?
Работает ли такой код на моей версии Битрикса?
Можно ли отменить операцию из After-обработчика?
Обработчик срабатывает на всех сайтах и инфоблоках. Как ограничить?
Что будет в проде, если обработчик упадёт с исключением?
В каком порядке вызываются несколько обработчиков одного события?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.