Перейти к содержанию

Зацикливание обработчиков событий

Задача: при изменении элемента инфоблока модифицировать другой элемент. Кейс может быть какой угодно - это и логирование, и деактивация основного товара, когда нет активных предложений, и изменение даты активности связанного элемента. Если создать обработчик, использующий метод CIBlockElement::Update, и повесить его на события OnBeforeIBlockElementUpdate / OnAfterIBlockElementUpdate, то:

вызов обработчика OnBeforeIBlockElementUpdate/OnAfterIBlockElementUpdate
    ....
CIBlockElement::Update
вызов обработчика OnBeforeIBlockElementUpdate/OnAfterIBlockElementUpdate
    ....
    CIBlockElement::Update

        ...
        итог - 500 (Internal Server Error)

Причина в том, что происходит рекурсивный вызов обработчика. Ниже приведен код, который избавляет от подобных проблем. В качестве примера взят обработчик OnAfterIBlockElementUpdate.

class myClass 
{ 
    protected static $handlerDisallow = false; 

    public static function iblockElementUpdateHandler(&$fields) 
    { 
        /* проверяем, что обработчик уже запущен */
        if (self::$handlerDisallow) 
            return; 
        /* взводим флаг запуска */
        self::$handlerDisallow = true;           
        /*  наш код, приводящий к вызову CIBlockElement::Update */ 
        ... 
        CIBlockElement :: Update (..., ...); 

        /* вновь разрешаем запускать обработчик */ 
        self::$handlerDisallow = false;
    } 
}

Пояснение. За основу взят класс, так как подобные решения в основном используются в собственных модулях. В классе имеется статическая булевая переменная - $handlerDisallow. По умолчанию она имеет значение false - нет запрета. В самом начале обработчика необходимо проверять ее значение. Если обработчик уже запущен, она будет равна true и выполнение необходимо прервать. Если же выполнять обработчик можно, необходимо присвоить этой переменной true на время выполнения всего обработчика. В конце необходимо флаг сбросить ($handlerDisallow), иначе до конца хита ваш обработчик не выполнится больше ни разу.

Если используется в качестве обработчика обычная функция, а не класс, то создайте статическую переменную внутри функции.

Можно дополнить класс возможностью блокировать работу обработчика "снаружи". Для этого измените тип переменной и добавьте три метода:

class myClass
{
    protected static $handlerDisallow = 0;

    public static function disableHandler()
    {
        self::$handlerDisallow--;
    }

    public static function enableHandler()
    {
        self::$handlerDisallow++;
    }

    public static function isEnabledHandler()
    {
        return (self::$handlerDisallow >= 0);
    }

    public static function iblockElementUpdateHandler(&$fields)
    {
        /* проверяем, что обработчик уже запущен */
        if (!self::isEnabledHandler())
            return;
        /* взводим флаг запуска */
        self::disableHandler();
        /*  наш код, приводящий к вызову CIBlockElement::Update */
    ...
    CIBlockElement :: Update (..., ...);

        /* вновь разрешаем запускать обработчик */
        self::enableHandler();
    }
}