Заказ оформился, а интеграция с CRM дернулась три раза - и в логах три одинаковые строки. Вы вставили AddEventHandler в init.php, но в поиске половина статей про облачную CRM, а не про CMS на вашем хостинге. Ниже - куда положить PHP-код, как подписаться на событие модуля sale или iblock, отличить модульное событие от почтового шаблона и добиться одного срабатывания на тестовом заказе.
Обработчик события - это PHP-функция или метод класса, которую Битрикс вызывает в нужный момент: после сохранения заказа, добавления элемента инфоблока, до отрисовки страницы. Глобальные подписки пишут в /local/php_interface/init.php через AddEventHandler или EventManager D7. Постоянные - в InstallEvents модуля через registerEventHandler. Два обработчика на одно событие дают два письма или два webhook. Для заказов проверяйте IS_NEW в OnSaleOrderSaved.
На практике типичная ошибка: скопировали обработчик из статьи про события, а в init.php уже висит подписка модуля маркетплейса - CRM уходит трижды. Например, ночной заказ уже в админке, а в логе три строки MY_ORDER_HANDLER.
Речь про CMS "1С-Битрикс: Управление сайтом" на сервере, не про Bitrix24. Модульное событие - сигнал из PHP (sale, iblock, main). Почтовый шаблон - другая цепочка: гайд B56. Если CRM дергается после каждого статуса заказа, сначала обработчики, потом обработка заказов.
Разберите, когда нужен обработчик на CMS
Обработчик нужен, когда стандартного поведения мало. Типичные задачи: отправить заказ во внешнюю CRM, проставить символьный код элементу инфоблока, записать метрику до вывода HTML. Это не настройка письма клиенту и не сценарий в облачной CRM.
| Что настраиваете | Где живет | Типичный триггер |
|---|---|---|
| Модульное событие (PHP) | init.php или модуль | OnSaleOrderSaved, OnAfterIBlockElementAdd |
| Почтовый шаблон | Админка "Почтовые события" | SALE_NEW_ORDER, FEEDBACK_FORM |
| Облачная CRM (automation) | Облако CRM | Роботы, бизнес-процессы - out of scope |
Триггеры: заказ (sale), элемент каталога (инфоблок), регистрация (main). Делайте: логику "после сохранения в БД" - через модульное событие. Не делайте: не путайте OnBeforeEventSend с OnSaleOrderSaved.
Выберите, где разместить код без правки ядра
Файл init.php подключается при каждом хите сайта. Правильный путь - /local/php_interface/init.php: он не затирается при обновлении ядра в /bitrix. Код в /bitrix/php_interface/init.php после апдейта может пропасть.
| Задача | Куда писать | Метод регистрации |
|---|---|---|
| Разовая доработка на проекте | /local/php_interface/init.php | AddEventHandler или addEventHandler |
| Функция в своем модуле | install/index.php, InstallEvents | registerEventHandler / unRegisterEventHandler |
| Правка ядра | /bitrix/modules/... | Запрещено |
В модуле подписку снимают при удалении через unRegisterEventHandler. Делайте: init.php в local. Не делайте: не регистрируйте в include.php без InstallEvents.
Зарегистрируйте обработчик через AddEventHandler и EventManager D7
AddEventHandler - классический API: модуль, событие, функция или метод. EventManager D7 - современный слой; ядро проксирует legacy в addEventHandlerCompatible. sort (по умолчанию 100) задает порядок: меньше число - раньше вызов.
Пример в init.php для инфоблока:
use Bitrix\Main\EventManager;
$eventManager = EventManager::getInstance();
$eventManager->addEventHandler(
'iblock',
'OnAfterIBlockElementAdd',
['\\Local\\Handlers\\IblockHandler', 'onAfterAdd']
);
В модуле при установке:
use Bitrix\Main\EventManager;
$em = EventManager::getInstance();
$em->registerEventHandler(
'sale',
'OnSaleOrderSaved',
'vendor.mymodule',
'\\Vendor\\Mymodule\\OrderHandler',
'onOrderSaved'
);
При удалении - unRegisterEventHandler с теми же аргументами. Старый код с $arFields по ссылке - addEventHandlerCompatible. В D7 аргументы: $event->getParameters().
- Откройте или создайте /local/php_interface/init.php.
- Найдите имя события в справочнике событий нужного модуля.
- Зарегистрируйте обработчик через EventManager::addEventHandler или AddEventHandler.
- Вынесите логику в класс с автозагрузкой, а не в гигантскую функцию в init.php.
- На staging оформите тестовое действие (заказ, элемент инфоблока).
- Убедитесь, что обработчик вызвался ровно один раз.
Делайте: registerEventHandler для кода в модуле, addEventHandler для проектных правок. Не делайте: не регистрируйте один и тот же callback и в init.php, и в InstallEvents - получите дубль.
Настройте типовые сценарии: заказ, инфоблок, пролог
OnAfterIBlockElementAdd - после элемента каталога: автокод, синхронизация. OnSaleOrderSaved вызывается при каждом сохранении заказа - не баг. Для "только новый" проверяйте IS_NEW:
public static function onOrderSaved(\Bitrix\Main\Event $event)
{
if ($event->getParameter('IS_NEW') !== true) {
return;
}
$order = $event->getParameter('ENTITY');
// отправка в CRM один раз
}
OnBeforeProlog - до вывода страницы; тяжелую логику лучше в агенты. Регистрация - main/OnAfterUserAdd (личный кабинет).
Делайте: для заказов фильтруйте IS_NEW или используйте OnSaleOrderBeforeSaved, если нужно изменить данные до записи. Не делайте: не вешайте тяжелый API-вызов на OnBeforeProlog без крайней необходимости.
Найдите причину двойного или тройного срабатывания
Схема диагностики: событие сработало → findEventHandlers показал N обработчиков → каждый дал side-effect → клиент получил N писем.
Алгоритм: заказ или элемент → EventManager::getInstance()->findEventHandlers('sale', 'OnSaleOrderSaved') → если записей больше одной, ищите дубль в init.php и модуле → removeEventHandler для теста → повторите сценарий.
Причины: дубль в init.php и модуле; OnSaleOrderSaved без IS_NEW при смене статусов; восстановленный init.php поверх старой подписки модуля.
Делайте: перед продом выведите список findEventHandlers на staging. Не делайте: не копируйте сниппеты с Habr в init.php, не проверив, нет ли того же в модуле маркетплейса.
Проверьте работу через журнал событий и отладку
На staging - Debug::writeToFile в /local/logs/. В проде - журнал событий (/bitrix/admin/event_log.php):
CEventLog::Add([
'SEVERITY' => 'INFO',
'AUDIT_TYPE_ID' => 'MY_ORDER_HANDLER',
'MODULE_ID' => 'sale',
'ITEM_ID' => $orderId,
'DESCRIPTION' => 'Обработчик OnSaleOrderSaved отработал',
]);
Отключить без удаления кода: removeEventHandler с теми же параметрами.
- Включите запись CEventLog::Add или Debug::writeToFile в начале обработчика.
- Выполните тестовый сценарий на staging.
- Откройте журнал событий или файл лога.
- Сверьте число записей с ожидаемым числом вызовов.
- Если записей больше - запустите findEventHandlers и уберите дубли.
- Зафиксируйте рабочую конфигурацию в документации проекта.
Если после правок интеграция все еще ведет себя странно, напишите нам - разберем цепочку на реальном проекте.
Делайте: логируйте ITEM_ID заказа или элемента. Не делайте: не оставляйте var_dump в обработчике на бою - сломаете вывод страницы.
Пройдите чек-лист перед выкладкой на прод
Workflow перед релизом: код в /local/ или модуле, один обработчик на событие, тест на staging. Дальше - запись в журнале, снятие тестовых removeEventHandler, деплой.
- Обработчик лежит в /local/php_interface/init.php или в InstallEvents модуля, не в /bitrix/modules.
- findEventHandlers показывает одну запись на нужное событие.
- Для заказов проверен IS_NEW, если нужен только первый вызов.
- Тестовый заказ или элемент на staging дал ожидаемый результат.
- В журнале событий есть тестовая запись с вашим AUDIT_TYPE_ID.
- При удалении модуля вызывается unRegisterEventHandler.
Кейсы - в портфолио. Делайте: документируйте подписки. Не делайте: API на прод без таймаута.
Автор: Максим Мольков, разработчик 1С-Битрикс.
Источники: документация EventManager D7, AddEventHandler, события сохранения заказа.
Частые вопросы
Чем обработчик события отличается от почтового шаблона?
Почтовый шаблон формирует письмо по типу события (SALE_NEW_ORDER) в админке. PHP-обработчик - ваш код на модульном событии (OnSaleOrderSaved), который может отправить API-запрос, записать лог или изменить данные. Письмо клиенту и интеграция с CRM - разные задачи; шаблоны настраивают в гайде B56.
Где лежит init.php в Битрикс?
Рабочий путь - /local/php_interface/init.php в корне сайта. Если папки local нет, создайте ее по структуре документации. Файл /bitrix/php_interface/init.php не рекомендуют: при обновлении продукта правки могут потеряться.
addEventHandler или registerEventHandler - что выбрать?
addEventHandler (или AddEventHandler) - для кода в init.php на конкретном проекте. registerEventHandler - когда обработчик входит в состав модуля: подписка сохраняется в базе и снимается через unRegisterEventHandler при удалении модуля. Не дублируйте оба варианта на одно событие.
Почему обработчик срабатывает дважды или трижды?
Чаще всего зарегистрированы два обработчика на одно событие - в init.php и в модуле. Реже OnSaleOrderSaved вызывается при каждом пересохранении заказа (статус, оплата). Проверьте findEventHandlers и добавьте проверку IS_NEW для сценария "только новый заказ".
Можно ли отключить обработчик без удаления кода?
Да. Вызовите EventManager::removeEventHandler с теми же параметрами module, event, class и method, что при регистрации. Для временной отладки на staging это быстрее, чем комментировать весь блок в init.php.
Это то же самое, что события в облачной CRM?
Нет. Роботы и JS BX.addCustomEvent - другой продукт. Статья про CMS "Управление сайтом" на PHP-хостинге: AddEventHandler, init.php, модули sale и iblock. Автоматизацию облачной CRM сюда не переносят.