Оптимизация производительности и безопасности — залог успешного проекта на 1С-Битрикс. Данный материал посвящен практическому применению «Подмена фабрики: Подробное руководство». Мы подготовили подробный разбор принципов работы и развернутые примеры интеграции.
Подмена фабрики
Теперь когда мы разобрались с основами можем перейти к частному и рассмотреть как именно действия добавляются к операциям.
Мы уже знаем что точкой входа в новое ядро является контейнер и что действие должно быть привязано к операции, но как именно происходит связь? Сначала мы определяем фабрику, операции которой подвергнутся изменению, а зачем мы вносим изменения в метод формирующий операцию у конкретной фабрики.
В этой статье мы поговорим про первую часть — подмену фабрики сущности.
Способы подмены
В этом разделе мы рассмотрим основные способы подмены фабрики:
1. Подмену контейнера
2. Подмена фабрики смарт-процесса по коду
Однако перед рассмотрением способов подмены давайте сначала определимся с самой фабрикой и решаемой задачей. Мы будем добавлять свое действие из статьи про основы к своему смарт-процессу при изменении элемента.
Отправная точка:
- Действие уже написано (
DevBook\EmailResponsibleNotify). - Смарт-процесс уже существует и код сущности известен (
164, находися в константеSUPER_ENTITY_TYPE_ID).
Наша цель: чтобы при изменении элемента в публичной части была отправка письма.
Для начала необходимо реализовать свою фабрику наследника — наслденика фабрики смарт-процесса:
// file: local/php_interface/classes/DevBook/Crm/MyProcessFactory.php
namespace DevBook\Crm;
use Bitrix\Main\Loader;
use Bitrix\Crm\Service\Factory\Dynamic;
Loader::requireModule('crm');
class MyProcessFactory extends Dynamic
{
}Затем необходимо подменить выполнение операции редактирования, поэтому внесем изменение в наш класс фабрики:
use Bitrix\Crm\Item;
use Bitrix\Crm\Service\Context;
use Bitrix\Crm\Service\Operation;
use Bitrix\Crm\Service\Operation\Update as UpdateOperation;
use DevBook\EmailResponsibleNotify;
class MyProcessFactory extends Dynamic
{
public function getUpdateOperation(Item $item, Context $context = null): UpdateOperation
{
$operation = parent::getUpdateOperation($item, $context);
return $operation->addAction(
Operation::ACTION_BEFORE_SAVE,
new EmailResponsibleNotify()
);
}
}Теперь необходимо только подменить результат метода getFactory для работы нашего механизма.
Подмена контейнера
Очевидно самый простой способ подменить фабрику — это подменить весь контейнер, т.е. результат Container::getInstance() и в методе getFactory вернуть свой класс-наследник.
В данном случае это возможно благодаря Локатору служб, ведь внутри getInstance метода находится получение объекта из контейнера:
public static function getInstance(): Container
{
return ServiceLocator::getInstance()->get('crm.service.container');
}Таким образом мы можем заменить его вызов и переопределить все что нам нужно.
Согласно нашей структуре кода создадим класс-наследник контейнера:
// file: /local/php_interface/classes/DevBook/Crm/Container.php
namespace DevBook\Crm;
use Bitrix\Main\Loader;
use Bitrix\Crm\Service\Container as BitrixContainer;
Loader::requireModule('crm');
class Container extends BitrixContainer
{
}А затем (согласно нашей структуре файлов) разместим код подмены контейнера в файле kernel.php
$serviceLocator->addInstanceLazy('crm.service.container', [
'className' => '\\DevBook\\Crm\\Container',
]);Таким образом в результате выполнения кода мы должны увидеть наш класс в качестве контейнера:
use \Bitrix\Main\Loader;
use \Bitrix\Crm\Service\Container;
Loader::requireModule('crm');
var_dump(get_class(Container::getInstance()));>Важное примечание: стандартно там должен быть наследник класса Bitrix\Crm\Service\Container, однако некоторые модули из маркетплейса подменяют контейнер в своих модулях. В случае обнаружения не своего и не стандартного класса обратитесь к разработчикам модуля для получения инструкций по кастомизации.
Теперь изменим контейнер чтобы он возвращал нашу фабрику. Согласно документации, для получения фабрики какой-то сущности, необходимо передать методу getFactory идентификатор сущности чью фабрику мы хотим получить. Именно этот метод нам нужно перекрыть в нашем контейнере.
public function getFactory(int $entityTypeId): ?Service\Factory
{
// Если наш тип - подменяем
if ( $entityTypeId == SUPER_ENTITY_TYPE_ID )
{
// Сгенерируем название сервиса ->
$identifier = static::getIdentifierByClassName(static::$dynamicFactoriesClassName, [$entityTypeId]);
// ... и проверим - вдруг уже есть объект класса?
if ( Main\DI\ServiceLocator::getInstance()->has($identifier) )
{
return Main\DI\ServiceLocator::getInstance()->get($identifier);
}
// Объекта нет. Получим 'объект смарт-процесса'
$type = $this->getTypeByEntityTypeId($entityTypeId);
if ( !$type )
{
// Не получилось, смарт-процесс удален
return null;
}
// Создадим фабрику, запомним ее
$factory = new \DevBook\Crm\MyProcessFactory($type);
Main\DI\ServiceLocator::getInstance()->addInstance(
$identifier,
$factory
);
// Вернем подмененную фабрику
return $factory;
}
// Если тип не наш - передаем в родительский метод
return parent::getFactory($entityTypeId);
}Теперь, мы можем открыть php-консоль в административном интерфейсе и выполнить код:
use Bitrix\Main\Loader;
use Bitrix\Crm\Service\Container;
Loader::requireModule('crm');
$factory = Container::getInstance()->getFactory( SUPER_ENTITY_TYPE_ID );
if ( $factory instanceof \DevBook\Crm\MyProcessFactory )
{
echo "It works!";
}
else
{
echo "Something wrong!";
}Мы увидим вывод It works!.
Отлично! Мы подменили фабрику через подмену контейнера.
Подмена фабрики без подмены контейнера
Пытливый читатель может заметить, что при разработке модулей подход с подменой контейнера может не сработать. Если внимательно приглядеться к методу getFactory можно заметить что мы вызываем из контейнера (который по факту является сервисом возвращаемым из DI\ServiceLocator) тот же DI\ServiceLocator для поиска существующих контейнеров.
А значит мы можем на любом событии (например OnPageStart) до момента его первого вызова подменить его сервис.
Для этого нам нужно получить подменяемое имя сервиса через метод \Bitrix\Crm\Service\Container::getIdentifierByClassName и реализовать аналогичный lazy-сервис подмены.
Пример кода который подменяет фабрику смарт-процесса без подмены контейнера:
// file: /local/php_interface/kernel.php
use \Bitrix\Main\Loader;
use \Bitrix\Crm\Service\Container;
use \DevBook\Crm\MyProcessFactory;
$serviceLocator->addInstanceLazy(
"crm.service.factory.dynamic.164",
[
'constructor' => function () {
Loader::requireModule('crm');
$type = Container::getInstance()->getTypeByEntityTypeId(164);
return new MyProcessFactory($type);
},
]
);💡 Экспертное мнение и рекомендации
Практические рекомендации:
- При написании кастомных компонентов для «Подмена фабрики: Подробное руководство» всегда разделяйте бизнес-логику и представление (шаблон).
- Используйте отладку SQL-запросов через конструкцию
$wpdb->last_queryили стандартный трекер Битрикса, чтобы контролировать нагрузку.
Похожая задача в бизнесе?
Помогу автоматизировать процессы и внедрить ИИ
Меня зовут Андрей Горошко. Я Full-stack разработчик и эксперт по 1С-Битрикс. Настраиваю CRM Битрикс24, разрабатываю защищенные API и создаю умных ИИ-агентов для автоматизации рутины.
или на почту: gorandvas@yandex.ru