Своё действие бизнес-процесса - папка, описание, форма настроек
Собираем своё действие для конструктора процессов: каталог, описание, класс с точкой входа, форма настроек и журнал.
Механика
Действие процесса - это каталог с файлами, а не запись в базе. Платформа обходит каталоги действий при открытии конструктора, поэтому новое действие появляется в списке сразу после появления файлов на диске.
Файлов минимум три, и у каждого своя работа. Описание рассказывает конструктору имя, класс и место в списке; класс выполняет работу; языковые файлы дают подписи для интерфейса на нужном языке.
Свойства действия объявляются в классе, а собираются формой настроек. Форму рисует статический метод класса, он же разбирает введённые значения обратно - поэтому имена полей формы и имена свойств держат согласованными.
Точка входа возвращает состояние, и это важнее, чем кажется. Завершённое действие отдаёт признак закрытия, а долгое - признак продолжения; во втором случае процесс остаётся на этом шаге и ждёт внешнего события.
Значения из процесса приходят в действие двумя разными путями, и их не путают. Простые настройки лежат в свойствах самого действия, а данные документа и переменные процесса берут через корневое действие и сервис документов.
Отладка идёт через журнал процесса, а не через вывод на экран. Действие пишет строки в журнал, и это единственный способ увидеть, что происходило внутри уже завершённого процесса.
Установка на боевом сайте - это копирование каталога. Своё действие кладут в каталог пользовательских действий, а тиражное решение переносит его туда при установке модуля.
Шаги
- Завести каталог действия рядом со своим кодом, а не в каталоге платформы.
- Написать описание: имя, класс, категорию и ограничение по типам документов.
- Написать класс с точкой входа и объявить свойства в конструкторе.
- Нарисовать форму настроек и разобрать её значения обратно в свойства.
- Положить языковые файлы, чтобы подписи не были жёстко зашиты в код.
- Проверить действие на тестовом процессе и прочитать журнал его работы.
Код
Раскладываем файлы действия:
/bitrix/activities/custom/vendorsendsms/├── .description.php - описание для конструктора процессов├── vendorsendsms.php - класс действия├── icon.gif - значок в списке действий└── lang/ru/ ├── .description.php - подписи описания └── vendorsendsms.php - подписи формы и ошибок// каталог custom - для своих действий, обновление его не трогает// каталог bitrix рядом - для штатных, и он перезаписывается целикомИмя каталога связывает всё вместе. Платформа ищет файл класса по имени каталога, поэтому переименование каталога без переименования файла даёт действие, которое видно в списке, но не работает.
Описываем действие для конструктора:
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
$arActivityDescription = [ 'NAME' => GetMessage('VENDOR_SMS_NAME'), 'DESCRIPTION' => GetMessage('VENDOR_SMS_DESC'), 'TYPE' => 'activity', // или condition для условия 'CLASS' => 'CBPVendorSendSms', 'JSCLASS' => 'BizProcActivity', 'CATEGORY' => ['ID' => 'other'], // раздел в списке действий 'ADDITIONAL_RESULT' => ['EntityFields'], // доп. результаты следующим шагам];// описание не возвращает массив, а заполняет переменную с этим именемТип определяет, чем будет действие: обычным шагом или условием ветвления. Описание при этом не возвращает массив, а заполняет переменную с оговорённым именем - это легаси-соглашение, и отход от него ломает загрузку описания.
Пишем класс действия:
class CBPVendorSendSms extends CBPActivity{ public function __construct($name) { parent::__construct($name); $this->arProperties = ['Title' => '', 'Phone' => '', 'Text' => '']; }
public function Execute() { $result = VendorSms::send($this->Phone, $this->Text); // своя отправка $this->WriteToTrackingService($result ? 'СМС отправлено' : 'Отказ отправки'); return CBPActivityExecutionStatus::Closed; // действие завершено }}Свойства читают как обычные поля объекта. Значения в них попадают из формы настроек, а выражения вида ссылок на поля документа платформа подставляет сама, до вызова точки входа.
Свои вызовы внутри точки входа оборачивают в перехват ошибок. Незакрытое исключение оставляет экземпляр процесса в подвешенном состоянии, и разбирать его потом приходится вручную по журналу.
Рисуем форму настроек:
public static function GetPropertiesDialog($documentType, $activityName, $template, $params, $variables, $currentValues = null, $formName = ''){ $dialog = new \Bitrix\Bizproc\Activity\PropertiesDialog(__FILE__, [ 'documentType' => $documentType, 'activityName' => $activityName, 'workflowTemplate' => $template, 'currentValues' => $currentValues, ]); return $dialog; // разметку берут из файла properties_dialog.php рядом}Разбираем значения формы обратно:
public static function GetPropertiesDialogValues($documentType, $activityName, &$template, &$params, &$variables, $currentValues, &$errors){ $errors = []; if (trim($currentValues['phone']) === '') { $errors[] = ['code' => 'empty', 'message' => 'Не указан телефон']; return false; // отказ не даёт сохранить настройку } // имена полей формы и имена свойств действия держат согласованными return true;}Проверка значений здесь дешевле, чем проверка при выполнении. Ошибка в настройке всплывёт при сохранении шаблона, а не ночью, когда процесс запустится по расписанию у реального документа.
Форма настроек - самая долгая часть работы. Разметку полей держат в отдельном файле рядом с классом, а сам метод только собирает объект диалога и передаёт ему текущие значения.
Выносим подписи в языковые файлы:
$MESS['VENDOR_SMS_NAME'] = 'Отправить СМС';$MESS['VENDOR_SMS_DESC'] = 'Отправляет сообщение через нашего провайдера';// подписи формы кладут в файл с именем действия, рядом с этимПодписи в языковых файлах нужны не ради переводов. Они отделяют тексты от кода, и менеджер, попросивший переименовать действие, больше не требует правки класса и выкладки на боевой сайт.
Пишем ход работы в журнал процесса:
$this->WriteToTrackingService('Отправляю на ' . $this->Phone);// журнал виден в карточке процесса: это единственная отладка для боевого сайта// вывод на экран из действия не работает: процесс идёт вне страницыОграничения
Действие видно не всем типам документов. Ограничение задают в описании, и без него своё действие появляется в конструкторе для всех сущностей сразу, включая те, где оно бессмысленно.
Долгие операции требуют признака продолжения. Действие, ожидающее ответа внешней системы, возвращает признак работы и завершается позже внешним событием; попытка дождаться ответа прямо в точке входа упирается в предел времени.
Каталог штатных действий трогать нельзя ни при каких обстоятельствах. Обновление перезаписывает его целиком, поэтому свои действия кладут в соседний каталог пользовательских - его обновление продукта не затрагивает.
Форма настроек действия живёт по своим правилам, отличным от обычных форм. Её разметку держат отдельным файлом рядом с классом, а проверку введённого - в своём методе, который платформа зовёт перед сохранением шаблона.
Ошибки внутри действия останавливают процесс. Незакрытое исключение оставляет экземпляр процесса в подвешенном состоянии, поэтому свои вызовы оборачивают и пишут отказ в журнал вместо падения.
Действие переживает обновления платформы, а его вызовы - не всегда. Методы чужих модулей, которые оно дёргает, меняются между версиями, и это проверяют при каждом крупном обновлении.
Типичные проблемы
Действие не появилось в конструкторе процессов.
Имя каталога, имя файла класса и имя в описании не совпадают. Платформа ищет файл по имени каталога и молча пропускает несовпадение.
Действие видно, но при выполнении процесс падает.
Класс не найден или в нём ошибка: имя класса в описании отличается от объявленного. Ошибку такого рода видно в журнале процесса, а не в конструкторе шаблона.
В настройках действия пустые подписи полей.
Языковые файлы не найдены: путь или имя файла не совпадают с именем действия. Подписи берутся по тем же именам, что и класс.
Процесс завис на своём действии.
Точка входа вернула признак продолжения, а внешнее событие так и не пришло. Для завершённой работы возвращают признак закрытия.
Действие сработало, но результата не видно.
Отладочный вывод шёл на экран, а процесс выполняется вне страницы. Ход работы пишут в журнал процесса.
После обновления платформы действие исчезло.
Каталог положили рядом со штатными действиями, а не в каталог пользовательских. Штатный каталог обновление перезаписывает целиком.
Частые вопросы
Где должен лежать каталог своего действия?
В каталоге пользовательских действий рядом со штатными: обновление перезаписывает только каталог штатных, а пользовательский не трогает. Имя каталога, имя файла класса и имя класса в описании держат согласованными.
Чем действие отличается от условия?
Типом в описании: обычное действие выполняет работу, условие управляет ветвлением шаблона. Класс и набор методов у них одинаковые, отличается только объявленный тип.
Как отладить действие на боевом сайте?
Записями в журнал процесса: он виден в карточке экземпляра и переживает завершение процесса. Вывод на экран не работает - процесс выполняется вне страницы, часто из задания по расписанию.
Что вернуть, если действие ждёт ответа внешней системы?
Признак продолжения работы: процесс останется на этом шаге, а завершит его внешнее событие, когда ответ придёт. Ждать ответа прямо в точке входа нельзя - упрётесь в предел времени выполнения.
Нужно ли перезапускать что-то после добавления файлов?
Обычно нет: конструктор обходит каталоги при открытии. Если действие не появилось, сначала проверяют совпадение имён, а уже потом сбрасывают кэш.
Смежное
- Бизнес-процессы - оглавление подтемы
- PHP-код в бизнес-процессе: переменные, типы, отладка - когда своё действие ещё не нужно
- Запуск бизнес-процесса из кода: шаблон, документ, повторы - как процесс вообще стартует
- Бизнес-процесс не запускается или стоит: разбор причин - что делать с зависшим экземпляром
- События, агенты и бизнес-процессы - устройство действий и их класса
- Модули и решения - устройство модулей целиком