Разработка кастомных обработчиков событий ORM 1С-Битрикс
ORM D7 — это не просто обёртка над SQL. Это полноценный слой объектов с собственной событийной моделью, которая работает иначе, чем «старые» события ядра. Если OnAfterIBlockElementUpdate срабатывает при любом обновлении элемента инфоблока через любой API, то события ORM привязаны к конкретной сущности и операции с ней через DataManager. Это даёт точечный контроль: перехватить добавление записи в таблицу b_sale_order_props_value без написания триггеров в БД.
Как устроены события ORM
Каждый DataManager-класс генерирует события в четырёх точках жизненного цикла записи:
-
OnBeforeAdd— до вставки, можно модифицировать поля или отменить операцию -
OnAfterAdd— после успешной вставки, полеIDуже доступно -
OnBeforeUpdate— до UPDATE, можно изменить передаваемые поля -
OnAfterUpdate— после успешного UPDATE -
OnBeforeDelete— до DELETE, можно отменить удаление -
OnAfterDelete— после DELETE
Событие формируется по шаблону: {ClassName}::On{Action}. Для класса Bitrix\Sale\Internals\OrderTable событие перед добавлением будет Bitrix\Sale\Internals\OrderTable::OnBeforeAdd.
Регистрация:
use Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler(
'sale',
'\Bitrix\Sale\Internals\OrderTable::OnAfterAdd',
[\MyProject\Handlers\OrderOrmHandler::class, 'onAfterAdd']
);
Объект события и доступные данные
В обработчик передаётся объект \Bitrix\Main\Entity\Event. Из него извлекаются параметры:
public static function onAfterAdd(\Bitrix\Main\Entity\Event $event): void
{
$result = $event->getParameter('result'); // объект Result с ID
$fields = $event->getParameter('fields'); // массив сохранённых полей
$newId = $result->getId();
$userId = $fields['USER_ID'] ?? null;
}
Для Before-событий — можно модифицировать поля через объект Result:
public static function onBeforeAdd(\Bitrix\Main\Entity\Event $event): \Bitrix\Main\Entity\EventResult
{
$result = new \Bitrix\Main\Entity\EventResult();
// Добавляем/изменяем поле перед сохранением
$result->modifyFields(['CREATED_BY' => \CUser::GetID()]);
// Или прерываем операцию
// $result->addError(new \Bitrix\Main\Error('Запрещено'));
return $result;
}
Практические сценарии
Аудит изменений. Логируем, кто и когда изменил запись в кастомной HL-таблице:
public static function onAfterUpdate(\Bitrix\Main\Entity\Event $event): void
{
$id = $event->getParameter('id')['ID'];
$fields = $event->getParameter('fields');
\MyProject\AuditLog::write([
'entity' => 'MyHlTable',
'entity_id' => $id,
'user_id' => \CUser::GetID(),
'changes' => json_encode($fields),
'timestamp' => new \Bitrix\Main\Type\DateTime(),
]);
}
Автозаполнение полей. При добавлении записи автоматически проставляем поля, которые нельзя доверить клиентскому коду:
public static function onBeforeAdd(\Bitrix\Main\Entity\Event $event): \Bitrix\Main\Entity\EventResult
{
$result = new \Bitrix\Main\Entity\EventResult();
$result->modifyFields([
'CREATED_AT' => new \Bitrix\Main\Type\DateTime(),
'STATUS' => 'DRAFT',
'HASH' => md5(uniqid('', true)),
]);
return $result;
}
Каскадное удаление. Перед удалением основной записи чистим связанные данные (которые ORM не удаляет автоматически без явно заданных связей):
public static function onBeforeDelete(\Bitrix\Main\Entity\Event $event): void
{
$id = $event->getParameter('id')['ID'];
// удаляем связанные записи через их DataManager
\MyProject\RelatedItemTable::deleteByParentId($id);
}
Разница между ORM-событиями и «старыми» событиями
| Параметр | ORM-события (D7) | Старые события (CMain) |
|---|---|---|
| Привязка | Конкретный DataManager-класс | Любой код, вызывающий API |
| Объект события | \Bitrix\Main\Entity\Event |
Массив $arParams |
| Модификация полей | Через EventResult::modifyFields() |
Через изменение переданного массива по ссылке |
| Отмена операции | EventResult::addError() |
Возврат false или специфично для события |
| Читаемость регистрации | Имя класса + операция | Строка-идентификатор события |
Важный нюанс: если запись создаётся через прямой SQL (Application::getConnection()->query(...)) или через старый API (CIBlockElement::Add()), ORM-события не срабатывают. ORM-события работают только при вызовах через DataManager::add(), ::update(), ::delete().
Highload-блоки и ORM-события
Для HL-блоков класс DataManager генерируется динамически. Найти имя класса:
$hlblock = \Bitrix\Highloadblock\HighloadBlockTable::getById($hlId)->fetch();
$entity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hlblock);
$className = $entity->getDataClass(); // что-то вроде HlbomOrderStatusTable
Затем регистрируем обработчик на этот класс. Если нужно работать с несколькими HL-блоками — удобно создать универсальный обработчик, который маршрутизирует по имени класса.
Сроки
| Задача | Срок |
|---|---|
| 2–4 обработчика для одной ORM-сущности (аудит, автозаполнение, валидация) | 2–4 дня |
| Система аудита для 5–10 ORM-таблиц с хранением истории изменений | 1–1.5 недели |
| Миграция «старых» обработчиков на ORM-события с тестированием | 1–2 недели |
ORM-события дают гранулярный контроль над жизненным циклом данных без триггеров в БД и без перехвата широких событий ядра. Правильно организованные обработчики — это аудит, валидация и бизнес-логика, которые живут рядом с данными, а не разбросаны по всему проекту.







