← Назад к заметкам

Заметки

Ошибка PHP в бизнес-процессе может скрыть факт запуска

Почему при ошибке в действии PHP-код бизнес-процесса может не остаться понятного следа, что нужная часть процесса запускалась.

Если действие PHP код падает до записи лога или служебной отметки, потом сложно понять, запускался ли этот участок бизнес-процесса вообще.

Суть проблемы

В бизнес-процессе может быть действие с произвольным PHP-кодом. Если этот код завершается ошибкой, выполнение обрывается раньше, чем процесс успевает записать диагностическую информацию.

Коротко

Проблема не только в самой ошибке PHP. Проблема в том, что после неё может быть непонятно, дошёл ли бизнес-процесс до этого действия.

Типовая цепочка выглядит так:

  • бизнес-процесс запускается по сделке, лиду или элементу списка;
  • процесс доходит до действия PHP код;
  • внутри PHP возникает ошибка;
  • следующие действия не выполняются;
  • служебная переменная, лог или отметка о старте не записываются;
  • при разборе кажется, что бизнес-процесс мог вообще не запускаться.

Поэтому старт важного участка лучше фиксировать до рискованного кода, а не после него.

Где встречается

Чаще всего это заметно в коробочном Bitrix24:

  • в действии PHP код внутри бизнес-процесса;
  • в процессах, которые запускаются автоматически;
  • при обращении к CRM из PHP-кода;
  • при работе с пользовательскими полями;
  • при вызове методов модулей без проверки подключения;
  • при использовании данных из переменных бизнес-процесса;
  • при запуске бизнес-процесса из другого бизнес-процесса.

Особенно неприятно это в автоматических процессах: пользователь не видит ошибку напрямую, а в истории элемента может не быть понятного объяснения.

Причина

Действие PHP код выполняется как часть бизнес-процесса. Если внутри него возникает ошибка, код ниже ошибки уже не выполняется.

Ошибка обрывает выполнение

Если сначала выполнить рискованное действие, а потом записать лог, то при ошибке лог может просто не появиться.

<?php

use Bitrix\Main\Loader;

Loader::includeModule('crm');

$deal_id = (int)$this->GetVariable('DEAL_ID');

updateDealData($deal_id);

$this->WriteToTrackingService('PHP-действие выполнено');

/**
 * Обновляет данные сделки.
 */
function updateDealData(int $deal_id): void
{
    if ($deal_id <= 0) {
        throw new RuntimeException('Не передан ID сделки');
    }

    $deal = new CCrmDeal(false);

    $deal->Update($deal_id, [
        'UF_CRM_EXAMPLE_FIELD' => 'Y',
    ]);
}

Если ошибка произойдёт внутри updateDealData, строка с WriteToTrackingService может не выполниться.

Лог мог не успеть записаться

В журнал бизнес-процесса нужно писать не только успешное завершение, но и факт входа в важный участок логики.

Иначе при разборе останется непонятный разрыв: бизнес-процесс вроде должен был сработать, но по журналу не видно, дошёл ли он до PHP-действия.

Решение

Перед рискованным кодом нужно заранее записывать техническую отметку: в лог бизнес-процесса, переменную, поле элемента или отдельный файл логов.

Фиксировать старт заранее

Минимальный порядок такой:

  • получить нужные входные данные;
  • записать в лог, что PHP-действие началось;
  • завернуть основной код в try/catch;
  • при ошибке записать текст ошибки;
  • после успешного выполнения записать отдельный лог завершения.

Так потом видно три состояния: действие не запускалось, действие стартовало и упало, действие стартовало и завершилось.

Пример для PHP-кода

В действии бизнес-процесса PHP код открывающий и закрывающий теги PHP указывать не нужно. В примере ниже они показаны только для оформления заметки.

<?php

use Bitrix\Main\Loader;

const CRM_MODULE_ID = 'crm';
const LOG_PREFIX = '[BP PHP]';

Loader::includeModule(CRM_MODULE_ID);

$deal_id = (int)$this->GetVariable('DEAL_ID');

writeProcessLog($this, 'Действие PHP код запущено', [
    'DEAL_ID' => $deal_id,
]);

try {
    updateDealData($deal_id);

    writeProcessLog($this, 'Действие PHP код завершено', [
        'DEAL_ID' => $deal_id,
    ]);
} catch (Throwable $exception) {
    writeProcessLog($this, 'Ошибка в действии PHP код', [
        'DEAL_ID' => $deal_id,
        'ERROR' => $exception->getMessage(),
    ]);
}

/**
 * Обновляет данные сделки.
 */
function updateDealData(int $deal_id): void
{
    if ($deal_id <= 0) {
        throw new RuntimeException('Не передан ID сделки');
    }

    $deal = new CCrmDeal(false);

    $deal->Update($deal_id, [
        'UF_CRM_EXAMPLE_FIELD' => 'Y',
    ]);
}

/**
 * Пишет сообщение в журнал бизнес-процесса.
 */
function writeProcessLog(object $activity, string $message, array $context): void
{
    $log_data = [
        'MESSAGE' => LOG_PREFIX . ' ' . $message,
        'CONTEXT' => $context,
    ];

    $activity->WriteToTrackingService(print_r($log_data, true));
}

Если нужно не только увидеть ошибку в журнале, но и остановить бизнес-процесс как ошибочный, после записи лога можно повторно выбросить исключение. Это зависит от того, как должен вести себя конкретный процесс.

Источники