Событие sale: защита от рекурсии в обработчиках
Защита обработчиков событий OnSaleOrderSaved от рекурсивных вызовов через статический флаг или проверку контекста выполнения.
Как использовать
- Скопируйте нужный фрагмент кода.
- Вставьте в свой проект и при необходимости измените под задачу.
- Проверьте зависимости и окружение (версии, переменные).
Защита обработчиков событий от рекурсивных вызовов при изменении заказа внутри обработчика. Используйте в обработчиках OnSaleOrderSaved, OnSaleOrderBeforeSaved для предотвращения бесконечных циклов.
use Bitrix\Main\Loader;
use Bitrix\Sale\Order;
/**
* Обработчик события OnSaleOrderSaved с защитой от рекурсии
*/
AddEventHandler('sale', 'OnSaleOrderSaved', function(&$order) {
// Статический флаг для отслеживания выполнения
static $inProgress = [];
$orderId = $order->getId();
// Проверяем, не выполняется ли уже обработка этого заказа
if (isset($inProgress[$orderId])) {
return; // Пропускаем, чтобы избежать рекурсии
}
// Устанавливаем флаг
$inProgress[$orderId] = true;
try {
// Ваша логика обработки заказа
// Например, изменение статуса, отправка уведомлений и т.д.
$statusId = $order->getField('STATUS_ID');
// Если нужно изменить заказ, делаем это аккуратно
if ($statusId === 'N') {
// Изменяем заказ
$order->setField('COMMENTS', 'Обработано обработчиком');
$result = $order->save();
// Если save() вызвал событие снова, флаг защитит от рекурсии
}
} finally {
// Снимаем флаг после завершения
unset($inProgress[$orderId]);
}
});
/**
* Альтернативный вариант: проверка через сессию/контекст
*/
AddEventHandler('sale', 'OnSaleOrderSaved', function(&$order) {
$orderId = $order->getId();
$contextKey = 'order_processing_' . $orderId;
// Проверяем через глобальную переменную
if (isset($GLOBALS[$contextKey])) {
return; // Уже обрабатывается
}
$GLOBALS[$contextKey] = true;
try {
// Логика обработки
// ...
} finally {
unset($GLOBALS[$contextKey]);
}
});
Usage:
// В /local/php_interface/init.php или отдельном файле обработчиков
// Код выше автоматически защитит от рекурсии
// Пример: обработчик, который меняет статус при сохранении
AddEventHandler('sale', 'OnSaleOrderSaved', function(&$order) {
static $processed = [];
$orderId = $order->getId();
if (isset($processed[$orderId])) {
return;
}
$processed[$orderId] = true;
// Изменяем статус (это вызовет событие снова, но флаг защитит)
if ($order->getField('STATUS_ID') === 'N' && $order->getPrice() > 10000) {
$order->setField('STATUS_ID', 'P');
$order->save(); // Вызовет OnSaleOrderSaved снова, но флаг предотвратит рекурсию
}
unset($processed[$orderId]);
});
Notes:
⚠️ Рекурсия возникает когда внутри обработчика OnSaleOrderSaved вызывается $order->save(), что снова вызывает событие. Статический флаг работает только в рамках одного запроса. Для защиты между запросами используйте флаг в БД или кеше. Флаг нужно снимать в finally блоке, чтобы он очистился даже при ошибке.