События в Битрикс: AddEventHandler, EventManager и собственные события

Событийная модель Битрикс позволяет добавлять логику в стандартные процессы без правки ядра: заказ, авторизация, инфоблок — всё через обработчики. Ниже — короткий обзор старого AddEventHandler, D7 EventManager и собственных событий. Развёрнутый practical-разбор приоритетов, отладки слушателей и защиты от двойного запуска — в отдельном гайде Bitrix D7 EventManager: подписка, приоритеты и отладка.

Старый способ: AddEventHandler

// В /local/php_interface/init.php
AddEventHandler('iblock', 'OnBeforeIBlockElementAdd', function(&$arFields) {
    // Принудительно ставим активность
    $arFields['ACTIVE'] = 'Y';
});

// Обработчик с классом
AddEventHandler('sale', 'OnSaleOrderSaved', ['\\MyModule\\Handlers\\OrderHandler', 'onOrderSaved']);

Compatible-события (большинство «классических» OnBefore/OnAfter) принимают аргументы по ссылке. Не подставляйте сюда сигнатуру Event $event, если документация модуля её не обещает.

Новый способ: EventManager D7

use Bitrix\Main\EventManager;

$eventManager = EventManager::getInstance();

// Для старых compatible-событий безопаснее так:
$eventManager->addEventHandlerCompatible(
    'iblock',
    'OnBeforeIBlockElementAdd',
    static function (&$arFields) {
        $arFields['ACTIVE'] = 'Y';
    }
);

// Для событий на Bitrix\Main\Event — addEventHandler + EventResult
$eventManager->addEventHandler(
    'sale',
    'OnSaleOrderBeforeSaved',
    ['\\MyModule\\Handlers\\OrderHandler', 'beforeSaved']
);

Путаница «почему обработчик молчит» чаще всего из-за неверного типа подписки или отсутствия автозагрузки класса. Чеклист и findEventHandlers — в гайде по EventManager.

Популярные события для переопределения

Модуль Событие Когда
main OnProlog Начало каждого запроса
main OnBeforeUserLogin Перед авторизацией
main OnAfterUserLogin После успешной авторизации
iblock OnBeforeIBlockElementAdd Перед добавлением элемента
iblock OnAfterIBlockElementAdd После добавления элемента
iblock OnBeforeIBlockElementDelete Перед удалением элемента
sale OnSaleOrderSaved Заказ сохранён
sale OnSaleStatusOrderChange Изменение статуса заказа

Создание собственного события

use Bitrix\Main\Event;
use Bitrix\Main\EventManager;

// Объявляем событие
$event = new Event('mymodule', 'OnBeforeProductExport', [
    'productId' => $id,
    'data'      => &$exportData,
]);
$event->send();

// Проверяем результаты обработчиков
foreach ($event->getResults() as $result) {
    if ($result->getType() === \Bitrix\Main\EventResult::ERROR) {
        throw new \RuntimeException($result->getParameters()['message']);
    }
}

Подписка на собственное событие из другого модуля:

EventManager::getInstance()->addEventHandler(
    'mymodule',
    'OnBeforeProductExport',
    function(\Bitrix\Main\Event $event) {
        $data = $event->getParameter('data');
        $data['extra_field'] = 'значение';
        return new \Bitrix\Main\EventResult(
            \Bitrix\Main\EventResult::SUCCESS,
            ['data' => $data]
        );
    }
);

Регистрация обработчиков в модуле

// InstallEvents / UnInstallEvents — через registerEventHandler
use Bitrix\Main\EventManager;

public function InstallEvents()
{
    EventManager::getInstance()->registerEventHandler(
        'iblock',
        'OnAfterIBlockElementAdd',
        $this->MODULE_ID,
        '\\MyModule\\IblockHandler',
        'afterElementAdd'
    );
}

namespace MyModule;
class IblockHandler
{
    public static function afterElementAdd(array &$arFields): void
    {
        if ((int)($arFields['IBLOCK_ID'] ?? 0) !== 10) {
            return;
        }
        // обработка
    }
}

registerEventHandler пишет в БД и переживает редеплой; addEventHandler в init.php живёт, пока подключён файл.

Итог

Для новых проектов берите EventManager D7 и явно различайте compatible-события и Bitrix\Main\Event. Простые глобальные хуки можно оставить в init.php. Ядро не трогайте — только события. Если нужна отладка «кто слушает» и защита от дублей — см. практический гайд по EventManager.