Розробка кастомних обробників подій 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-подій дають гранульний контроль над життєвим циклом даних без триггерів у БД і без перехоплення широких подій ядра. Правильно організовані обробники — це аудит, валідація і бізнес-логіка, які живуть поруч з даними, а не розкидані по всьому проекту.







