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

Заметки

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

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

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

Проблема

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

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

Обычно цепочка такая:

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

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

Причина

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

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

<?php

use Bitrix\Main\Loader;

if (!Loader::includeModule('crm')) {
    throw new RuntimeException('Не удалось подключить модуль 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);

    $fields = [
        'UF_CRM_EXAMPLE_FIELD' => 'Y',
    ];

    $is_updated = $deal->Update($deal_id, $fields);

    if (!$is_updated) {
        throw new RuntimeException('Не удалось обновить сделку');
    }
}

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

Решение

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

Порядок лучше сделать таким:

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

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

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

<?php

use Bitrix\Main\Loader;

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

if (!Loader::includeModule(CRM_MODULE_ID)) {
    throw new RuntimeException('Не удалось подключить модуль CRM');
}

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

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

try {
    updateDealData($deal_id);

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

    throw $exception;
}

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

    $deal = new CCrmDeal(false);

    $fields = [
        'UF_CRM_EXAMPLE_FIELD' => 'Y',
    ];

    $is_updated = $deal->Update($deal_id, $fields);

    if (!$is_updated) {
        throw new RuntimeException('Не удалось обновить сделку');
    }
}

/**
 * Формирует сообщение для журнала бизнес-процесса.
 */
function buildProcessLogMessage(string $message, array $context): string
{
    $log_data = [
        'MESSAGE' => LOG_PREFIX . ' ' . $message,
        'CONTEXT' => $context,
    ];

    return print_r($log_data, true);
}

В примере ошибка сначала записывается в журнал, а потом снова выбрасывается через throw $exception. Так бизнес-процесс остаётся ошибочным, но в журнале уже есть понятная причина.

Если процесс должен продолжать работу после ошибки, исключение можно не пробрасывать дальше. Но отметку о старте всё равно лучше писать до основного кода.