[spoiler]
После непродолжительной (или очень продолжительной) отладки выясняется то, о чем следовало бы подумать сразу - идет рекурсивный вызов обработчика. Я бы не заострялся на этом моменте, но неоднократное появление на сайте идей и форуме пожеланий внедрить возможность отключения механизма событий заставляет "расставить все точки над i".
Ниже приведен код, который избавляет от подобных проблем. В качестве примера взят обработчик 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; } } |
Если Вы используете в качестве обработчика обычную функцию, не класс - не беда. Создайте статическую переменную внутри функции.
Update. Дополним класс возможностью блокировать работу обработчика "снаружи". Для этого изменим тип переменной и добавим три метода:
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(); } } |
Но надо учесть, что такой предохранитель работает исключительно в рамках одного хита.
1. Собственно глобальная видимость
2. "Потроха" обработчика выносятся в общее пространство.
Сейчас дополню пост.
Сели бы все дружно спроектировали правильно и отрефакторили.
Я понимаю что все хотят вытрепнуться, мол я умею использовать конструкции PHP, но чтобы работало нужно сделать хотя бы так:
Есть такая вещь, как культура программирования. Она включает в себя понятие "верни, как было". Если бы у методов disableHandler и enableHandler был модификатор private или protected - да, Ваша реализация имела бы смысл. Но методы могут быть вызваны кем угодно и где угодно (public) неограниченное количество раз. Значит, ВСЕГДА вызвав один (disableHandler) необходимо вызвать и второй (enableHandler). В противном случае могут возникать трудноотлаживаемые ошибки.
то он будет биться в истерических конвульсиях, не понимая почему его "суперкод" не работает.
По сути вопроса, я бы все-таки поддержал идею с отключением событий. Практически во всех event-based системах такой функционал предусмотрен. В данном случае, нужно иметь дополнительный параметр в функции Update, Add, Delete - вызывать ли события или нет. Это было бы более аккуратным и гибким механизмом.
Так, как для каждого товара корзины например этот обработчик вызывается, то мне нужно не один раз, чтобы это сработало. использую такой метод для создания комплектов товаров (где на один или два товара, когда они входят в комплект задается меньшая цена нежели цена, когда товар покупается отдельно). для таких целей я создал отдельный тип цены и когда нужно посчитать для товаров, которые не в комплекте, то этот тип цены пробую пропускать при вызове OnGetOptimalPrice.
Заранее спасибо за ответ. извиняюсь если очень запутанно написал.
return CCatalogProduct::GetOptimalPrice($productID, $quantity, $arUserGroups, $renewal,$arPrices1);
//вновь разрешаем запускать обработчик
self::$handlerDisallow = false;
У меня обмен данными идет с внешним приложением, и там стоит подобная система обработчиков, которая при изменении данных, отправляет их в бирикс.
К примеру в приложении добавили новость и приложение отправляет запрос на создание новости в битрикс, если тут не отключить события, то сработает обработчик события и битрикс отправит запрос на добавление новой новости в приложение и круг замкнется.