При разработке и поддержке проектов на платформе 1С-Битрикс критически важно использовать современные стандарты D7 API. В этом руководстве мы детально изучим тему «Общее: Подробное руководство», разберем архитектурные особенности и предоставим готовые к интеграции примеры кода.
Общее
Основной точкой расширения для старого ядра являеется механизм событий — это тот иструмент который позволяет добавить свое поведение к уже существующему процессу. Инструмент достаточно простой, гибкий, но содержащий в себе не мало опасности: непрогнозируемый состав полей, зацикливание обработчиков, повторяющиеся запросы. Все это нарушает идеи нового API: не способствует уменьшению дублированию кода, не позволяет покрывать их тестами, не позволяет уменьшить число ошибок.
Аналогом обработчиков событий являются «Действия» — специальный класс реализующий Bitrix\Crm\Service\Operation\Action, который добавляется к операции.
Как добавляется действие к операции?
На данный момент добавление своего действия к операции осуществляется через подмену фабрики.
Преимущества
Чтобы ответить на вопрос «какие преимущества действий перед обработчиками событий?» необходимо рассмотреть недостатки самих обработчиков событий:
- непрогнозируемый состав полей,
- отсутствие доступа к значениям до (на событии after),
- повторяющиеся запросы,
Что имеется ввиду под «непрогнозируемым составом полей»?
Это не «появляющиеся» (или исчезающие) параметры события, а именно поля. Давайте посмотрим на событие crm::OnAfterCrmDealUpdate — у нас есть набор полей ($arFields) который используется при изменении сделки. Важно понимать что в $arFields передаются не все поля, а только те которые изменяются (или были затронуты в период изменения).
Давайте рассмотрим пример обработчика:
$eventManager->addEventHandlerCompatible(
'crm',
'OnAfterCrmDealUpdate',
function( &$arFields )
{
if ( empty($arFields['UF_CRM_1233432423']) )
{
if ($arFields['UF_CRM_DFG4AS64DA2D'] < 100)
{
// Send email to user
}
}
return true;
}
);Наше ожидание: при изменении сделки, если у нас заполнено значение UF_CRM_1233432423 И значение UF_CRM_DFG4AS64DA2D меньше 100 то мы хотим отправить письмо сотруднику.
Наша реальность: если мы откроем карточку сделки, нажмем на поле «Название» и поменяем только его, то сотруднику придет письмо.
Почему так получилось?
Потому что в $arFields придут значения ключей: TITLE, ~DATE_MODIFY, MODIFY_BY_ID, а значит $arFields['UF_CRM_1233432423'] будет равно null и мы успешно пройдем условие с пустотой и ровно та же самая ситуация повторится со вторым условием.
Чтобы такого не случилось мы будем вынуждены дописывать дополнительные условия к нашей логике и код превратиться в нечто такое:
$eventManager->addEventHandlerCompatible(
'crm',
'OnAfterCrmDealUpdate',
function( &$arFields )
{
if (
array_key_exists('UF_CRM_1233432423', $arFields)
&& empty($arFields['UF_CRM_1233432423']) )
{
if (
array_key_exists('UF_CRM_DFG4AS64DA2D', $arFields)
&& $arFields['UF_CRM_DFG4AS64DA2D'] < 100
)
{
// Send email to user
}
}
return true;
}
);Что имеется ввиду под «отсутствие доступа к значениям до (на событии after)»?
Давайте рассмотрим гипотетическую ситуацию каждый раз, когда изменяется поле UF_CRM_1233432423 и значение поля UF_CRM_DFG4AS64DA2D меньше 100 мы должны отправлять пользователю уведомление.
Из предыдущего недостатка мы узнали что на событие поступают не все поля, а значит мы должны достать эти поля из базы данных.
И поскольку наше событие выполняется «после» изменения, значит что в базе данных значения уже изменены и нам необходимо настраивать совместную работу пары обработчиков.
// file: ...local/php_interface/events.php
$eventManager->addEventHandlerCompatible(
'crm',
'OnBeforeCrmDealUpdate',
['\\DevBook\\CrmHandler', 'beforeUpdate']
);
$eventManager->addEventHandlerCompatible(
'crm',
'OnAfterCrmDealUpdate',
['\\DevBook\\CrmHandler', 'afterUpdate']
);
// file: ...local/php_interface/classes/DevBook/CrmHandler.php
namespace DevBook;
class CrmHandler
{
protected static $objBeforeSave;
public static function beforeUpdate(&$arFields)
{
if ( array_key_exists('UF_CRM_1233432423', $arFields) )
{
static::$objBeforeSave = Container::getInstance()->getFactory(\CCrmOwnerType::Deal)->getItem($arFields['ID']);
}
}
public static function afterUpdate(&$arFields)
{
if ( array_key_exists('UF_CRM_1233432423', $arFields) )
{
$isChanged_UF_CRM_1233432423 = false;
$isChanged_UF_CRM_1233432423 = (
$arFields['UF_CRM_1233432423'] != static::$objBeforeSave->get('UF_CRM_1233432423')
&& empty($arFields['UF_CRM_1233432423']) )
);
$valueUF_CRM_DFG4AS64DA2D = array_key_exists('UF_CRM_DFG4AS64DA2D', $arFields)
? $arFields['UF_CRM_DFG4AS64DA2D']
; null;
if ( is_null($valueUF_CRM_DFG4AS64DA2D) )
{
$valueUF_CRM_DFG4AS64DA2D = static::$objBeforeSave->get('UF_CRM_DFG4AS64DA2D');
}
if ( $isChanged_UF_CRM_1233432423 %% $valueUF_CRM_DFG4AS64DA2D < 100 )
{
// Send email to user
}
}
return true;
}
}Что происходит в коде?
В методе beforeUpdate мы подписываемся на событие «перед» изменением сделки и в случае изменения поля мы получаем исходные данные (то что было до изменения).
В методе afterUpdate мы знаем что сохранение выполнено успешно (в противном случае вызова события бы не было) и поскольку наш обработчик события не знает про предыдущий обработчик мы должны снова проверить что интересуемое действие (изменение UF_CRM_1233432423) было произведено.
>Технически, мы можем проверить это и через наличие статического объекта, но такой способ дает больше наглядности.
Далее мы проверяем что значение поля было изменено (сравнивая с предыдущим значением), получаем значение для поля UF_CRM_DFG4AS64DA2D (если его не было в запросе) и проверяем наше условие — Изменение поля UF_CRM_1233432423 и значение поля UF_CRM_DFG4AS64DA2D меньше 100.
>В этом разделе скрывается еще одна очень редкая логическая ловушка — если в момент выполненния нашего afterUpdate нам нужно будет выполнить изменение другой сделки с теми же полями то при определенной ситуации отправка письма может быть выполнена не тому пользователю, поскольку мы дважды войдем в before с разными параметрами и заменим из общий static::$objBeforeSave на данные второй сделки.
Что имеется ввиду под «повторяющимися запросами»?
В предыдущем примере мы рассмотрели ситуацию, когда для реализации бизнес-логики нам требуется выполнить запрос в базу данных. А теперь представим себе случай, когда у нас 4 механики, каждая из которых разрабатывается независимо от других. Получается чем больше независимых бизнес-правил и спецэфичной логики тем больше однотипных запросов мы делаем в базу данных.
Как нам помогут действия для операции?
В разделе Добавление действий вы узнаете подробнее о действиях, сейчас я продемонстрирую тот же самый обработчик события переписанный в действия.
namespace DevBook;
use \Bitrix\Crm\Item;
use \Bitrix\Main\Loader;
use \Bitrix\Main\Result;
use \Bitrix\Crm\Service\Operation\Action;
Loader::requireModule('crm');
class EmailResponsibleNotify extends Action
{
public function process(Item $item): Result
{
$result = new Result();
if (
$this->getItemBeforeSave()->isChanged('UF_CRM_1233432423')
&& empty($item->get('UF_CRM_1233432423')))
&& $item->get('UF_CRM_DFG4AS64DA2D') < 100
)
{
// Send email to user
}
return $result;
}
}Почему здесь намного меньше кода, чем в предыдущих обработчиках событий?
Все дело в механизме действий — он выполняется над объектом Item, поэтому мы можем быть уверены что значения этих полей есть*(1). Поскольку действие выполняется после выполнения физического сохранения у нас есть возможность получить доступ к копии объекта, которая была до сохранения элемента и проверить изменялось ли это поле.
В результате мы получили:
1. Простой и лаконичный код: никаких технических проверок на наличие полей.
2. Улучшенная производительность: не делаем лишних запросов, не регистрируем лишних обработчиков событий, не триггеримся на любое срабатывание.
3. Избавились от редкого бага с дублированием
💡 Экспертное мнение и рекомендации
Практические рекомендации:
- При написании кастомных компонентов для «Общее: Подробное руководство» всегда разделяйте бизнес-логику и представление (шаблон).
- Используйте отладку SQL-запросов через конструкцию
$wpdb->last_queryили стандартный трекер Битрикса, чтобы контролировать нагрузку.
Похожая задача в бизнесе?
Помогу автоматизировать процессы и внедрить ИИ
Меня зовут Андрей Горошко. Я Full-stack разработчик и эксперт по 1С-Битрикс. Настраиваю CRM Битрикс24, разрабатываю защищенные API и создаю умных ИИ-агентов для автоматизации рутины.
или на почту: gorandvas@yandex.ru