PHP
#bitrix#sale#events#php#recursion#onSaleOrderSaved

Событие sale: защита от рекурсии в обработчиках

Защита обработчиков событий OnSaleOrderSaved от рекурсивных вызовов через статический флаг или проверку контекста выполнения.

Как использовать

  1. Скопируйте нужный фрагмент кода.
  2. Вставьте в свой проект и при необходимости измените под задачу.
  3. Проверьте зависимости и окружение (версии, переменные).

Защита обработчиков событий от рекурсивных вызовов при изменении заказа внутри обработчика. Используйте в обработчиках 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 блоке, чтобы он очистился даже при ошибке.