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

Заметки

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

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

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

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

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

Суть

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

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

  • бизнес-процесс запускается по сделке, лиду или элементу списка;
  • процесс доходит до действия 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-действие началось;
  • завернуть основной код в 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);
}

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

Источники