Блокировка заказа в Sale для защиты от гонок
Проблема/контекст
В sale заказ одновременно могут менять несколько процессов: админка, AJAX-обработчик, обмен, агент. Без синхронизации вы получаете потерю изменений: один запрос перезаписывает поля/коллекции, которые уже изменил другой.
Решение с кодом
Используйте встроенную блокировку Bitrix\Sale\Order::lock()/unlock() и всегда снимайте её в finally. Перед блокировкой проверьте статус через getLockedStatus(): если заказ уже заблокирован другим пользователем — корректно завершайте операцию (HTTP 409/ошибка).
<?php
declare(strict_types=1);
use Bitrix\Main\Context;
use Bitrix\Main\Loader;
use Bitrix\Sale\Order;
Loader::includeModule('sale');
$request = Context::getCurrent()->getRequest();
$orderId = (int)$request->getPost('orderId');
if ($orderId <= 0)
{
throw new \Bitrix\Main\ArgumentException('Invalid orderId');
}
// 1) Проверяем, нет ли чужой блокировки
$lockStatus = Order::getLockedStatus($orderId)->getData(); // LOCK_STATUS, LOCKED_BY, DATE_LOCK
if (($lockStatus['LOCK_STATUS'] ?? null) === Order::SALE_ORDER_LOCK_STATUS_RED)
{
// Здесь лучше вернуть 409 Conflict и показать, кем/когда заблокировано
throw new \RuntimeException('Order is locked by another user');
}
$lockResult = Order::lock($orderId);
if (!$lockResult->isSuccess())
{
throw new \RuntimeException('Failed to lock order: ' . implode('; ', $lockResult->getErrorMessages()));
}
try {
// 2) Загружаем и меняем заказ строго внутри блокировки
$order = Order::load($orderId);
if (!$order)
{
throw new \RuntimeException('Order not found');
}
// Пример: безопасное изменение статуса и сохранение
$r = $order->setField('STATUS_ID', 'P'); // ваш код статуса
if (!$r->isSuccess())
{
throw new \RuntimeException('Status update failed: ' . implode('; ', $r->getErrorMessages()));
}
$save = $order->save();
if (!$save->isSuccess())
{
throw new \RuntimeException('Order save failed: ' . implode('; ', $save->getErrorMessages()));
}
} finally {
// 3) Снимаем блокировку всегда, даже при исключениях
Order::unlock($orderId);
}
Практические детали:
- Блокируйте как можно ближе к месту изменения и держите блокировку минимальное время.
- Не делайте внутри блокировки долгие операции (внешние API, генерация файлов, длительные расчёты). Сначала подготовьте данные, затем коротко: lock → load → change → save → unlock.
- Если операция может выполняться без веб-пользователя (агент/CLI), заранее продумайте контекст прав:
unlock()проверяет права текущего пользователя, поэтому тип выполнения важен.
Итог
Order::lock()/unlock() — простой способ избежать потери изменений при параллельных запросах. Ключевое правило — проверка статуса и снятие блокировки в finally.
Похожие советы