Development of PHP hooks for 1C-Bitrix events
The event model of Bitrix is the main mechanism for extending the system without modifying the core. When you need to execute an action on order creation, catalog element change, user authorization, or 1C exchange — you write an event handler. The problem is that improper use turns handlers into a bottleneck: they slow down hits, create cyclic calls, break standard logic. Below — analysis of event architecture and typical mistakes.
How events work in Bitrix
The Bitrix core generates events at key points: before operation (Before events) and after (After events). A handler is registered via EventManager:
use Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler(
'sale', // module
'OnSaleOrderBeforeSaved', // event
['MyHandler', 'onBeforeOrderSave'] // callback
);
Handler registration — in init.php file (/local/php_interface/init.php or /bitrix/php_interface/init.php). For modules — in installEvents() method.
Before events allow modifying data before saving or cancel operation. Return EventResult with type ERROR — operation is interrupted. After events — for reacting to completed action: send notification, write log, update related data.
Catalog of key events
| Module | Event | When triggered | Type |
|---|---|---|---|
iblock |
OnBeforeIBlockElementAdd |
Before adding infoblock element | Before |
iblock |
OnAfterIBlockElementUpdate |
After updating element | After |
sale |
OnSaleOrderBeforeSaved |
Before saving order | Before |
sale |
OnSalePayOrder |
On order payment | After |
sale |
OnSaleStatusOrder |
On order status change | After |
sale |
OnSaleBasketItemRefreshData |
On basket recalculation | Before |
catalog |
OnBeforePriceUpdate |
Before price update | Before |
main |
OnAfterUserAuthorize |
After user authorization | After |
main |
OnBeforeProlog |
Before page generation | Before |
Full list — in module files: /bitrix/modules/{module}/lib/events.php or in dev.1c-bitrix.ru documentation.
Handler architecture: how to stay organized
On a real project there are dozens of handlers. Without organization, init.php becomes a dump. Recommended structure:
/local/php_interface/
├── init.php → only require registration files
├── handlers/
│ ├── sale.php → sale module handler registration
│ ├── iblock.php → iblock module handler registration
│ └── main.php → main module handler registration
├── classes/
│ ├── SaleHandler.php → classes with sale handler logic
│ ├── IblockHandler.php
│ └── MainHandler.php
Each handler class — static methods. One method — one event. Inside method — minimal logic: input validation, service class call, result return.
Typical mistakes and how to avoid them
Cyclic calls. OnAfterIBlockElementUpdate handler inside itself updates infoblock element → OnAfterIBlockElementUpdate triggers again → infinite recursion. Solution — static flag:
class IblockHandler
{
private static bool $isProcessing = false;
public static function onAfterUpdate($arFields): void
{
if (self::$isProcessing) return;
self::$isProcessing = true;
// ... logic
self::$isProcessing = false;
}
}
Heavy operations in Before events. OnSaleOrderBeforeSaved is called at every order recalculation — which happens several times during checkout. If inside is an HTTP request to external API (address check, delivery calculation) — checkout slows down. Solution: move heavy operations to After events or to queue (agents, \Bitrix\Main\Event with deferred handling).
Missing error handling. Before event handler returns EventResult::ERROR but doesn't set error message. User sees empty error. Always pass meaningful errorMessage to EventResult constructor.
Dependency on handler order. If two modules hook to one event, call order depends on sort parameter when registering (default — 100). If your handler must execute first — set sort=50.
Debugging handlers
Standard way — Bitrix\Main\Diag\Debug::writeToFile(). Writes to /local/php_interface/debug.log (or path set in .settings.php).
More systematic approach — perfmon module. Shows which handlers are registered on each event and how much time each consumes. Enabled in Settings → Performance → Performance Panel.
For Before events of sale module — special note: handler chain breaks on first ERROR. If your handler doesn't execute — check if another handler registered with smaller sort returns ERROR.
Module vs init.php
For one-off handlers (specific to project) — init.php + files in /local/. For reusable logic (external service integration, used on multiple projects) — own module.
Module registers handlers in installEvents() and removes in uninstallEvents(). On module deactivation handlers automatically turn off — which doesn't happen with code in init.php.
Timeline
| Task | Scope | Duration |
|---|---|---|
| 1–3 simple handlers (notifications, logging) | init.php + classes | 2–3 days |
| Complex logic (integration, order validation, price recalculation) | Module + tests + documentation | 1–1.5 weeks |
| Refactoring existing handlers (remove from init.php, eliminate conflicts) | Audit + reorganization | 1–2 weeks |
Bitrix's event model is powerful but requires discipline. Each handler is code that executes on every hit (if event is frequent). One inefficient handler on OnBeforeProlog adds 50 ms to all pages — multiply by a million hits per day.







