Новое универсальное API для CRM в Битрикс24
Вся кастомизация в новом API CRM построена на паттерне Service Locator (Сервис-локатор). В основу нового api заложены такие паттерны как: Factory Method (Фабричный Метод).
Container, Factory, Operation и Action: основные понятия
В новом API появилось много не совсем очевидных слов поэтому давайте разберемся в терминологии:
Точкой входа в весь механизм является контейнер, контейнер это singleton-класс который с одной стороны обеспечивает простой доступ к сервисам из локатора сервисов, с другой стороны является абстрактной фабрикой для фабрик по работе с сущностями.
Задача: обеспечить точку входа.
Мы уже знаем что точкой входа в новое API является контейнер, основной рабочей лошадкой является фабрика. Именно она выполняет функцию унификации в коде и отвечает за единообразие всех методов универсального API.
Каждая фабрика является наследником абстрактного класса \Bitrix\Crm\Service\Factory который содержит универсальные методы, работающие на любом наследнике.
Паттерн: реализация паттерна Фабричный Метод (Factory Method).
Задача: фабрика создает объекты Operation (операции) для работы с элементами CRM.
Как работает: DinamicFactory наследуется от базового Service\Factory.
Нас интересует три главных метода фабрики, которые возвращают объекты операций:
getAddOperation()→Operation\Add(создание)getUpdateOperation()→Operation\Update(обновление)getDeleteOperation()→Operation\Delete(удаление)
Термин Operation (Операция) наиболее емко описывает необходимый набор действий для достижений конкретной цели.
Представим себе элемент crm, который мы хотим добавить. Добавление, это действие которое мы хотим совершить и запись в базу лишь часть действительно большого процесса — добавления элемента. Здесь мы можем попасть в логическую ловушку и подумать что процесс добавление элемента это и есть фактическое добавление записи в базу данных однако не стоит забывать что в нем так же участвуют обработчики событий, проверка прав, нормализация данных, поисковая индексация и другие важные этапы, в том числе запуск бизнес-процессов запускаемых при добавлении элемента.
Задача: инкапсулирует всю логику выполнения действия над элементом (добавление, изменение, удаление).
Как работает: каждая операция содержит массив Action, отдельные шаги с бизнес-логикой. Операция последовательно выполняет эти шаги.
Аналогом обработчиков событий в Битрикс, являются Action (Действие), специальный класс реализующий Bitrix\Crm\Service\Operation\Action, который добавляется к операции.
Задача: это отдельный, атомарный кусок бизнес-логики представляющий ваш кастомный код.
Как работает: вас должен интересовать один главный метод process(). Он принимает объект Bitrix\Crm\Item (элемент, над которым проводится операция) и возвращает Bitrix\Main\Result (успех или ошибку).
С какими сущностями работает новое API
Новый API полностью поддерживается для:
- Смарт-процессов
- Счетов
- Коммерческих предложений
- Документов (подсистема подписи)
Опциональная поддержка, в зависимости от настроек:
- Лиды
- Сделки
- Контакты
- Компании
Как узнать включено или нет новое API
Проверить, включено ли новое API, можно только через php-код. Для проверки можно использовать специальные классы настроек:
use \Bitrix\Crm\Settings;
use \Bitrix\Main\Loader;
Loader::requireModule('crm');
var_dump([
'deal' => Settings\DealSettings::getCurrent()->isFactoryEnabled(),
'lead' => Settings\LeadSettings::getCurrent()->isFactoryEnabled(),
'contact' => Settings\ContactSettings::getCurrent()->isFactoryEnabled(),
'company => Settings\CompanySettings::getCurrent()->isFactoryEnabled(),
]);
А также можно использовать объекты сущностей:
use \Bitrix\Main\Loader;
Loader::requireModule('crm');
var_dump([
'deal' => ( new \CCrmDeal() )->isUseOperation(),
'lead' => ( new \CCrmLead() )->isUseOperation(),
'contact' => ( new \CCrmContact() )->isUseOperation(),
'company' => ( new \CCrmCompany() )->isUseOperation(),
]);
Пример результата когда использование выключено:
array(4) {
["deal"] => bool(false)
["lead"] => bool(false)
["contact"] => bool(false)
["company"] => bool(false)
}
Как включить/отключить новое API
Включить механизм можно двумя способами:
- Через код
- Через браузер
Включить через код
Для включения через код, необходимо задействовать механизм настроек, переводим флаг в положение true:
\Bitrix\Main\Loader::requireModule('crm');
\Bitrix\Crm\Settings\DealSettings::getCurrent()->setFactoryEnabled(true);
Аналогично можно сделать для других сущностей, достаточно вместо DealSettings использовать LeadSettings, ContactSettings и CompanySettings соответственно.
Включить через браузер
Для включения через браузер, достаточно перейти в раздел CRM и добавить параметр ?enableFactory=Y. Включить новую настройку может любой пользователь с правами на доступ в CRM.
Отключение
Отключение механизма производится аналогично подключению, с единственной разницей, необходимо передавать false в php-коде и N в адресной строке.
Service Locator
Вся кастомизация в новом API построена на паттерне Service Locator (Сервис-локатор).
Регистрация сервисов
Битрикс заранее регистрирует в системе все классы-фабрики для работы с сущностями CRM. Это происходит в конфигурационных файлах, например в bitrix/modules/crm/.settings.php:
'crm.service.factory.contact' => [
'className' => '\\Bitrix\\Crm\\Service\\Factory\\Contact',
],
Получение сервиса
В коде любой модуль или компонент может получить стандартную фабрику для работы с контактами, получаем стандартную фабрику контактов через Service Locator:
$factory = \Bitrix\Main\DI\ServiceLocator::getInstance()
->get('crm.service.factory.contact');
Подмена сервиса с целью кастомизации
Чтобы добавить свою логику, мы создаем собственный класс, наследуем его от стандартного и переопределяем нужные методы:
// наш кастомный класс фабрики
class ContactFactory extends \Bitrix\Crm\Service\Factory\Contact
{
// здесь мы переопределим методы, например, для добавления обработчиков
}
Затем мы подменяем стандартный сервис в локаторе на наш:
// подменяем стандартную фабрику своей
\Bitrix\Main\DI\ServiceLocator::getInstance()->addInstance('crm.service.factory.contact', new ContactFactory());
Результат подмены
Теперь любой код в системе, который запрашивает фабрику контактов, будет получать наш экземпляр ContactFactory. Таким образом, наша логика будет выполняться везде, где используется фабрика:
$factory = \Bitrix\Main\DI\ServiceLocator::getInstance()->get('crm.service.factory.contact');
Упрощенное получение через Container
Для удобства, разработчики Битрикс24 добавили класс-обертку Container. Стандартный способ получить фабрику через Container, после подмены он тоже вернет наш кастомный класс:
$factory = \Bitrix\Crm\Service\Container::getInstance()->getFactory(\CCrmOwnerType::Contact);
Как добавить свой обработчик
Вся магия происходит внутри методов фабрики. Вы переопределяете нужный метод, например для обновления getUpdateOperation, получаете стандартную операцию и добавляете в нее свой Action:
// переопределяем метод получения операции обновления
public function getUpdateOperation(Item $item, Context $context = null): Operation\Update
{
// 1. получаем стандартную операцию обновления
$operation = parent::getUpdateOperation($item, $context);
// 2. добавляем свой Action на этап "ПЕРЕД сохранением"
$operation->addAction(
Operation::ACTION_BEFORE_SAVE,
new ExampleAction()
);
return $operation;
}
Метод addAction() принимает два ключевых параметра:
Operation::ACTION_BEFORE_SAVEдо сохранения элемента в БДOperation::ACTION_AFTER_SAVEпосле успешного сохранения элемента в БД
Чтобы добавить свою логику, вам нужно:
- В фабрике переопределить
getAddOperationдля кастомизации при создании,getUpdateOperationдля кастомизации при обновлении,getDeleteOperationдля кастомизации при удалении - Добавить свой
Actionв нужную операцию с помощьюaddAction(), указав этап выполненияACTION_BEFORE_SAVEилиACTION_AFTER_SAVE
Как добавить свой обработчик в смарт-процесс
Разберем на примере смарт-процесса под названием Рекламации с идентификатором 1036:
Создаем класс-фабрику, которая будет возвращать операцию с вашим обработчиком:
/local/modules/hmarketing.rest/lib/Crm/OrderFactory.php<?php
namespace Hmarketing\Rest\Crm;
use Bitrix\Crm\Service\Factory\Dynamic;
use Bitrix\Crm\Service\Context;
use Bitrix\Crm\Service\Operation;
use Bitrix\Crm\Model\Dynamic\TypeTable;
use Bitrix\Crm\Item;
class OrderFactory extends Dynamic
{
// идентификатор типа смарт-процесса
protected int $entityTypeId = 1036;
public function __construct()
{
$type = TypeTable::getByEntityTypeId($this->entityTypeId)->fetchObject();
if (!is_null($type))
{
parent::__construct($type);
}
else
{
// важно, обработайте случай, если тип не найден
throw new \Exception("Smart process type with ID {$entityTypeId} not found.");
}
}
// переопределяем метод получения операции обновления
public function getUpdateOperation(Item $item, Context $context = null): Operation\Update
{
// 1. получаем стандартную операцию обновления
$operation = parent::getUpdateOperation($item, $context);
// 2. добавляем свой Action на этап "ПЕРЕД сохранением"
$operation->addAction(
Operation::ACTION_BEFORE_SAVE,
new \Hmarketing\Rest\Action\OrderAction()
);
return $operation;
}
}
Регистрируем нашу фабрику вместо стандартной. Это нужно сделать в точке входа, например в init.php. Ключ сервиса формируется по шаблону crm.service.factory.dynamic.{ID_СУЩНОСТИ}:
/local/php_interface/init.php<?php
// подключаем модуль crm
\Bitrix\Main\Loader::includeModule('crm');
// подключаем кастомный модуль где лежат все файлы hmarketing.rest
\Bitrix\Main\Loader::includeModule('hmarketing.rest');
// подменяем фабрику для конкретного смарт-процесса (ID=1036)
\Bitrix\Main\DI\ServiceLocator::getInstance()->addInstance(
// ключ для динамического типа
'crm.service.factory.dynamic.1036',
new Hmarketing\Rest\Crm\OrderFactory()
);
Создаем класс, в котором будет реализована ваша логика обработки:
/local/modules/hmarketing.rest/lib/Action/OrderAction.php<?php
namespace Hmarketing\Rest\Action;
use Bitrix\Main\Result;
use Bitrix\Main\Error;
use Bitrix\Crm\Item;
use Bitrix\Crm\Service\Operation\Action;
// ваш кастомный класс фабрики
class OrderAction extends Action
{
public function process(Item $item): Result
{
$result = new Result();
// для примера, проверим поле сумма
if ($item->get('UF_CRM_3_1766692211591') < 0)
{
$result->addError(new Error("Сумма заказа не может быть отрицательной!"));
}
// возвращаем результат операции
return $result;
}
}
Создаем свойство типа число:
Если введем отрицательное число, получим ошибку:


