b24 / devtools
Requires
- php: >=8.1.0
- phpoffice/phpspreadsheet: 1.19
This package is auto-updated.
Last update: 2025-01-10 21:23:45 UTC
README
- Установка
- Подключение
- Регистрация библиотеки как Модуль в системе Bitrix
- Смарт-процессы
- CRUD над таблицей b_crm_entity_relation
- Работа с денежными полями
- Highload-блоки
- StepProcessing (Пошаговая обработка)
Установка
composer require b24/devtools
Подключение
Для использования инструментов необходимо подключить автозагрузчик Composer. Пример подключения:
local/php_interface/init.php
require $_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php';
Регистрация библиотеки как Модуль в системе Bitrix
С помощью этого можно регистрировать свои контроллеры не создавая модуль. В init.php добавить:
new \B24\Devtools\Application( new \B24\Devtools\Configuration([ 'controllers' => [ 'namespaces' => [ '\\Some\\Namespace' => 'custom' ] ] ]) ); // ИЛИ new \B24\Devtools\Application( (new \B24\Devtools\Configuration()) ->setController('\\Some\\Namespace', 'custom') ->setController(...) ); \CModule::IncludeModule('b24.devtools'); // Вернёт true
Из js будет доступна отправка запроса в контроллер:
BX.ajax.runAction('b24:devtools.custom.ControllerName.actionName')
Смарт-процессы
Создание смарт-процесса с чистого листа
$mapper = \B24\Devtools\Crm\Smart\Mapper::create(title: 'TEST', code: 'TEST', name: 'TEST'|null);
При успешном создании отдаст объект класса \B24\Devtools\Crm\Smart\Mapper
class Mapper { public readonly \B24\Devtools\Crm\Smart\SmartDynamic $smart; public readonly string $entityName; public function __construct( public readonly int $id, public readonly int $entityTypeId, public readonly string $code, ) {} }
Удаление смарт-процесса
По символьному коду, либо по ENTITY_ID сущности
\B24\Devtools\Crm\Smart\Mapper::deleteByCodeOrEntityId($code|$entityTypeId) // Либо \B24\Devtools\Crm\Smart\Mapper::deleteByCodeOrEntityIdIfExists($code|$entityTypeId)
По ID из таблицы b_crm_dynamic_type
\B24\Devtools\Crm\Smart\Mapper::deleteById($id); // Либо \B24\Devtools\Crm\Smart\Mapper::deleteByIdIfExists($id)
Подмена сервис контейнера
Пример использования:
use Module\Helpers\Crm\Replacement\Container; new Container([ 'TEST' => FactoryTest::class ]);
Аргументом в конструктор передаётся массив, где ключом CODE смарт-процесса, значением неймспейс его фабрики. Позволяет вызывать события ДО и ПОСЛЕ на элементе смарт-процесса.
Пример фабрики
use Bitrix\Crm\Item; use Bitrix\Crm\Service; use Bitrix\Crm\Service\Context; use Bitrix\Crm\Service\Operation; class FactoryTest extends Service\Factory\Dynamic { public function getAddOperation(Item $item, Context $context = null): Operation\Add { $operation = parent::getAddOperation($item, $context); $operation->addAction( Operation::ACTION_BEFORE_SAVE, new AddHandler() ); return $operation; } }
Обработчик события на Добавление элемента
use Bitrix\Crm\Service\Operation; use Bitrix\Main\Result; use B24\Devtools\Crm\ResultOperationTrait; class AddHandler extends Operation\Action { use ResultOperationTrait; public function process(\Bitrix\Crm\Item $item): Result { return $this ->error('Ошибка 1') ->error('Ошибка 2') ->result(); } }
Работа с сущностью смарт-процесса
\B24\Devtools\Crm\Smart\SmartProcess насследует \B24\Devtools\Crm\Smart\SmartDynamic
$entityTypeId = \B24\Devtools\Crm\Smart\SmartProcess::getIdByCode('TEST'); $smart = new \B24\Devtools\Crm\Smart\SmartProcess($entityTypeId); // Либо так: $smart = new \B24\Devtools\Crm\Smart\SmartProcess('TEST'); $smart->getFactory(); // Вернёт фабрику смарт-процесса $smart->getFactoryId(); // ID смарт-процесса $smart->getEntityName(); // Название объекта смарт-процесса, CRM_2 (например) $smart->compileClass(); // Отдаст неймспейс класса ORM смарт-процесса $smart->getContainer(); // Отсюда же можно вытащить сервис контейнер $smart->getRelationManager(); // RelationManager
CRUD над таблицей b_crm_entity_relation
// Чтение связей // $children = \B24\Devtools\Crm\Relation\Manager::searchChildren(\CCrmOwnerType::Quote, 1) ->getAll(); // Массив из ItemIdentifier всех привязанных детей к Предложению $children = \B24\Devtools\Crm\Relation\Manager::searchChildren(\CCrmOwnerType::Quote, 1) ->withEntityTypeId(152); // Массив ID всех привязанных детей-элементов смартпроцесса с ID 152 $children = \B24\Devtools\Crm\Relation\Manager::searchChildren(\CCrmOwnerType::Quote, 1) ->withOne(function (\Bitrix\Crm\ItemIdentifier $identifier) { return $identifier; // Вернёт массив из ItemIdentifier return $identifier->getEntityId(); // Вернёт массив из ID ItemIdentifier }); // Если заменить метод searchChildren на searchParents, то будут искаться родители //
// Обновление связей // \B24\Devtools\Crm\Relation\Manager::update(\CCrmOwnerType::Quote, 1, 152, 1) ->isParent() // Например если в связи надо отвязать родителя (Предложения) и привязать к другому Предложению ->on(\CCrmOwnerType::Quote, 2) // Привязываем к Предложению с ID = 2 ->replace(); // Замена
// Удаление какой-то одной связи // \B24\Devtools\Crm\Relation\Manager::deleteOne(\CCrmOwnerType::Quote, 1, 152, 1); // У Предложения с ID = 1 удаляем все связи со смарт-процессом, у которого ID = 152 \B24\Devtools\Crm\Relation\Manager::deleteWithType(\CCrmOwnerType::Quote, 1, 152);
// Создание связей // Создаст у Предложения с ID = 1 связь (ребёнка) со смарт-процессом с ID = 152 // \B24\Devtools\Crm\Relation\Manager::create(\CCrmOwnerType::Quote, 1, 152, 1);
Работа с денежными полями
$moneyField = '155|USD'; $rateUsdToRub = 93.22; // либо $money = new \B24\Devtools\Data\MoneyField(155, 'USD'); $money = \B24\Devtools\Data\MoneyField::parse($moneyField) ->math(function (&$price) use ($rateUsdToRub) { $price = $price * $rateUsdToRub; }) ->setCurrency('RUB') ->round(2); echo (string) $money; // 14449.1|RUB
Highload-блоки
Упрощённая работа с хайлод блокоми. Теперь хайлод блоки описываются как модели:
use B24\Devtools\HighloadBlock\ActiveRecord; use B24\Devtools\HighloadBlock\Fields\Enumeration; use B24\Devtools\HighloadBlock\Fields\EnumValue; use B24\Devtools\HighloadBlock\Fields\Field; class ElementsCatalogHighload extends ActiveRecord { public function getName(): string { return 'ElementsCatalog'; } public function getTableName(): string { return 'elements_catalog'; } public function ruName(): string { return 'Элементы каталога'; } public function enName(): string { return 'Elements catalog'; } protected function getFields(string $entityId): array { return [ new Field( entityId: $entityId, fieldName: 'ENUMERATION', userTypeId: UserTypeEnum::ENUMERATION, multiple: true, enum: new Enumeration( [ new EnumValue( 'Школа43', 'SCHOOL43' ), ], ) ), ]; } }
Метод getFields - возвращает список полей Highload блока. По умолчанию битрикс создаёт столбец ID, его указывать не нужно.
в конструктор класса Field передаётся 3 обязательных параметра, остальные можно не заполнять
class Field { public function __construct( private string $entityId, private string $fieldName, private string|UserTypeEnum $userTypeId, private bool $multiple = false, private bool $mandatory = true, private array $editFormLabel = [], private array $listColumnLabel = [], private array $listFilterLabel = [], private array $errorMessage = [], private array $helpMessage = [], private ?array $settings = null, private ?Enumeration $enum = null ) {} }
Под капотом этот класс превращается в массив:
[ 'ENTITY_ID' => $entityId, // Название сущности 'FIELD_NAME' => $fieldName, // Название поля. Можно задать без UF_ - он проставится автоматически 'USER_TYPE_ID' => $userTypeId, // Тип пользовательского поля, можно найти в UserTypeEnum описание всех полей для хайлод блока 'MULTIPLE' => $multiple, // Множественное поле 'MANDATORY' => $mandatory, // Обязательность заполнения 'EDIT_FORM_LABEL' => $editFormLabel ?? $fieldName, // массив языковых сообщений вида array("ru"=>"привет", "en"=>"hello") 'LIST_COLUMN_LABEL' => $listColumnLabel ?? $fieldName, 'LIST_FILTER_LABEL' => $listFilterLabel ?? $fieldName, 'ERROR_MESSAGE' => $errorMessage ?? $fieldName, 'SETTINGS' => $settings, // массив с настройками свойства зависимыми от типа свойства. Проходят "очистку" через обработчик типа PrepareSettings. 'ENUM' => $enum, ]
Создание и Удаление хайлод блока
$hl = ElementsCatalogHighload(); $hl->createHL(); // Создание $hl->dropHL(); // Удаление
События для хайлод блока
в init.php:
ElementsCatalogHighload::events() ->onAdd(ElementsCatalogEvent::class) ->onAfterAdd(ElementsCatalogEvent::class) ->onBeforeAdd(ElementsCatalogEvent::class) ->onDelete(ElementsCatalogEvent::class) ->onAfterDelete(ElementsCatalogEvent::class) ->onBeforeDelete(ElementsCatalogEvent::class) ->onUpdate(ElementsCatalogEvent::class) ->onAfterUpdate(ElementsCatalogEvent::class) ->onBeforeUpdate(ElementsCatalogEvent::class)
Обработчик события:
use B24\Devtools\HighloadBlock\Operation\EventTrait; use Bitrix\Main\ORM\Event; class ElementsCatalogEvent { use EventTrait; public static function onBeforeAdd(Event $event) { self::setError($event, 'Ошибка_1'); } }
для событий был создан трейт B24\Devtools\HighloadBlock\Operation\EventTrait для удобной записи ошибки.
Если событие называется onBeforeAdd, то и метод будет статический и называться onBeforeAdd.
Миграции полей хайлод-блоков
У класса, который насследует B24\Devtools\HighloadBlock\ActiveRecord, есть обязательный метод getFields, который описывает поля хайлод блока, метод migrate будет отслеживать этот список
$hl = ElementsCatalogHighload(); $hl->migrate();
Если удалилось какое то поле из метода getFields(), он будет отслежен и удалён из базы. Так же метод отслеживает изменения списка в enum:
protected function getFields(string $entityId): array { return [ new Field( entityId: $entityId, fieldName: 'ENUMERATION', userTypeId: UserTypeEnum::ENUMERATION, multiple: true, enum: new Enumeration( [ new EnumValue( value: 'Школа43', xmlId: 'SCHOOL43', def: false, // поле поумолчанию? sort: 500 ), ], ) ), ]; }
Если в enum будет добавлен ещё один EnumValue, то он добавится так же в список БД, уникальность определяется по xmlId. Если удалить из списка какой нибудь EnumValue, он так же удалится из базы Если у EnumValue изменится либо value, либо def - значение списка обновится
Удобная вставка/обновление записи в highload-блоке
Запись:
$hl = new ElementsCatalogHighload(); $transfer = $hl->getTransfer(); $result = $transfer ->set('NAME', 'Имя') ->setEnumByXmlId('ENUMERATION', 'SCHOOL43') ->saveExistingFile('FILE', 'log.txt') // Все пути будут складываться от $_SERVER['DOCUMENT_ROOT'] . '/' ->setDateTime('DATE', new \Bitrix\Main\Type\DateTime()) // второй аргумент такой уже поумолчанию ->setBoolean('BOOLEAN', true) ->save();
Обновить запись:
$hl = new ElementsCatalogHighload(); $transfer = $hl->getTransfer(id: 1); $result = $transfer ->set('NAME', 'Имя') ->save();
во всех методах set можно вначале не указывать у названия поля UF_ - он проставится автоматически если его нет. Так же нет необходимости запоминать какое-то множественное поле чтобы вместо строки задать массив строк, это уже умеет делать Хелпер, он смотрит описанные поля в методе getFields и если $multiple = true, он считает это поле множественным:
$hl = new ElementsCatalogHighload(); $transfer = $hl->getTransfer(); $transfer ->setEnumByXmlId('ENUMERATION', 'SCHOOL43') ->setEnumByXmlId('ENUMERATION', 'SCHOOL44')
Под капотом в UF_ENUMERATION будет записан массив из двух значений.
StepProcessing (Пошаговая обработка)
Документация Bitrix: https://dev.1c-bitrix.ru/api_d7/bitrix/ui/stepprocessing/examples.php
Генерация объекта
use B24\Devtools\Process\UI\Fillers\Buttons; use B24\Devtools\Process\UI\Fillers\Handler; use B24\Devtools\Process\UI\Fillers\Messages; use B24\Devtools\Process\UI\Fillers\Queue; use B24\Devtools\Process\UI\Step\Result; use B24\Devtools\Process\UI\StepProcessing; $process = new StepProcessing( id: 'uniq_id', controller: 'module:name.controllers.Process', // только контроллеры модуля messages: new Messages( DialogTitle: "Title", DialogSummary: "Description", DialogStartButton: "StartButton", DialogStopButton: "StopButton", DialogCloseButton: "CloseButton", RequestCanceling: "Canceling...", RequestCanceled: "Canceled", RequestCompleted: "Completed", DialogExportDownloadButton: "ExportDownloadButton", DialogExportClearButton: "ExportClearButton", ) ); $process->setButtons(new Buttons(start: true, close: true, stop: true)); $process->setQueue( new Queue(action: 'run', title: 'Page generation 1', params: ['page' => 1]), new Queue(action: 'run', title: 'Page generation 2', params: ['page' => 2]), new Queue(action: 'run', title: 'Page generation 3', params: ['page' => 3]), new Queue(action: 'run', title: 'Page generation 4', params: ['page' => 4]), ); $process->setHandlers( new Handler( callbackType: Handler::StateChanged, body: ' function (state, result) { if (state !== "' . Result::COMPLETED . '") { return; } console.log(result); } ' ) ); $process->initJS();
<div id="processing" class="ui-btn ui-btn-light-border" onclick="<?=$process->showDialog() ?>" > Process </div>
Динамическая подгрузка очереди
<script> let process = <?=$process->toJsObject() ?>.setQueue([...queue]) </script>
Экшен Контроллера для Пошаговой обработки
use B24\Devtools\Process\UI\Step\Result; public function runAction(): array { $page = $this->request->get('page') ?? 1; return (new Result( status: true, processedItems: $page, totalItems: $totalPages ))->toArray(); } public function cancelAction(): array { return Result::cancel(); }
Пошаговая запись в Excel-файл
use B24\Devtools\Excel\IteratorManager; $header = ['ID', 'Name', 'Last Name']; $limit = 2; $filePath = '/upload/file.xlsx'; $rows = [ ['1', 'John', 'Hovewer'], ['2', 'Freken', 'Bock'], ]; // Первая итерация $manager = new IteratorManager( filePath: $filePath, page: 1, limit: $limit ); $manager->setHeaderRow($header) ->setRows($rows) ->saveFile(); // Вторая итерация $manager = new \B24\Devtools\Excel\IteratorManager( filePath: $filePath, page: 2, limit: $limit ); $manager->setRows($rows) ->saveFile();