InsertIgnore стратегия для безопасной вставки без дублирования
Проблема дублирования записей
При массовой вставке данных в базу через ORM часто возникает проблема дублирования по уникальным полям. Стандартный метод add() генерирует ошибку при попытке вставить запись с существующим первичным ключом или уникальным индексом. Приходится вручную проверять существование записи через getList() перед вставкой, что неэффективно при массовых операциях.
Стратегия InsertIgnore
С версии 22.0 в Bitrix ORM появилась стратегия InsertIgnore, которая использует SQL-конструкцию INSERT IGNORE. Если запись с таким ключом существует, она игнорируется без ошибки. Это атомарная операция на уровне СУБД, что обеспечивает корректность даже при конкурентных запросах.
Переопределение стратегии в Table-классе
Самый простой способ — переопределить метод getAddStrategy() в вашем Table-классе:
namespace Mycompany\MyModule;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Data\AddStrategy;
class UserTokenTable extends DataManager
{
public static function getTableName()
{
return 'b_user_token';
}
public static function getMap()
{
return [
'ID' => ['data_type' => 'integer', 'primary' => true, 'autocomplete' => true],
'USER_ID' => ['data_type' => 'integer', 'required' => true],
'TOKEN' => ['data_type' => 'string', 'required' => true],
];
}
// Переопределяем стратегию для всех операций add()
protected static function getAddStrategy(): AddStrategy\Contract\AddStrategy
{
return new AddStrategy\InsertIgnore(static::getEntity());
}
}
// Теперь add() использует INSERT IGNORE автоматически
$result = UserTokenTable::add([
'ID' => 1,
'USER' => 1,
'TOKEN' => 'abc123'
]);
// Если запись существует - игнорируется без ошибки
// $result->isSuccess() вернёт true
// $result->getId() вернёт ID существующей записи
Массовая вставка с InsertIgnore
Для массовой вставки данных используйте метод addMulti():
use Bitrix\MyModule\UserTokenTable;
$tokens = [
['ID' => 10, 'USER_ID' => 1, 'TOKEN' => 'token1'],
['ID' => 11, 'USER_ID' => 2, 'TOKEN' => 'token2'],
['ID' => 12, 'USER_ID' => 3, 'TOKEN' => 'token3'],
['ID' => 10, 'USER_ID' => 1, 'TOKEN' => 'token1'], // дубликат - будет проигнорирован
];
// Все записи вставляются одним запросом
$result = UserTokenTable::addMulti($tokens);
// Проверка успешности
if ($result->isSuccess()) {
// Операция выполнена, дубликаты проигнорированы
}
Указание уникальных полей
По умолчанию InsertIgnore проверяет первичный ключ. Если нужно проверять другие поля, передайте их в конструктор:
protected static function getAddStrategy(): AddStrategy\Contract\AddStrategy
{
// Проверять уникальность по полям USER_ID и TOKEN
return new AddStrategy\InsertIgnore(
static::getEntity(),
['USER_ID', 'TOKEN']
);
}
Проверка изменений базы
Метод isDBChanged() в результате показывает, была ли реально изменена база:
$result = UserTokenTable::add(['USER_ID' => 1, 'TOKEN' => 'abc']);
if ($result->isSuccess()) {
if ($result->getData()['isDBChanged']) {
// Запись была вставлена
} else {
// Запись уже существовала и была проигнорирована
}
}
Важные ограничения
InsertIgnore работает только с таблицами, имеющими первичный ключ или уникальный индекс. Стратегия не поддерживает таблицы без ограничений уникальности. При попытке использовать InsertIgnore на несовместимой таблице будет выброшено исключение NotSupportedException.
Когда использовать InsertIgnore
Применяйте InsertIgnore для импорта данных, синхронизации справочников, сохранения логов без дублей, кэширования токенов. Для случаев, когда нужно обновить существующую запись, используйте стратегию Merge вместо InsertIgnore.
Похожие советы
Безопасная работа с JSON через Bitrix\Main\Web\Json
Проблема
При работе с JSON в 1С-Битрикс многие разработчики используют нативные функции json_encode() и json_decode() без должной обработки ошибок. Это приводит к silent-ошибкам: когда данные не кодируются корректно, функция возвращает false или null, но код продолжает выполняться. В результате возникают сложно диагностируемые проблемы с невалидными данными.
Решение
Класс Bitrix\Main\Web\Json предоставляет безопасные методы для работы с JSON, автоматически выбрасывая исключения при ошибках кодирования/декодирования.
Базовое использование
use Bitrix\Main\Web\Json;
use Bitrix\Main\ArgumentException;
// Безопасное кодирование
try {
$jsonString = Json::encode($data);
} catch (ArgumentException $e) {
// Обработка ошибки кодирования
}
// Безопасное декодирование
try {
$array = Json::decode($jsonString);
} catch (ArgumentException $e) {
// Обработка ошибки декодирования
}
Преимущества класса
Класс автоматически использует оптимальные опции по умолчанию:
JSON_HEX_TAG,JSON_HEX_AMP,JSON_HEX_APOS,JSON_HEX_QUOT— защита от XSSJSON_UNESCAPED_UNICODE— корректная работа с кириллицейJSON_INVALID_UTF8_SUBSTITUTE— замена невалидных символов UTF-8JSON_THROW_ON_ERROR— автоматический выброс исключений
Валидация JSON
Класс предоставляет метод для проверки валидности JSON-строки:
use Bitrix\Main\Web\Json;
use Bitrix\Main\Application;
$request = Application::getInstance()->getContext()->getRequest();
$jsonString = $request->getPost('json_data') ?? '';
if (!Json::validate($jsonString)) {
throw new \Bitrix\Main\ArgumentException(
"Невалидный JSON в параметре json_data"
);
}
$data = Json::decode($jsonString);
Кастомные опции кодирования
Вы можете переопределить опции при необходимости:
use Bitrix\Main\Web\Json;
// Форматированный вывод с отступами
$prettyJson = Json::encode(
$data,
Json::DEFAULT_OPTIONS | JSON_PRETTY_PRINT
);
// Без экранирования слешей для URL
$jsonForApi = Json::encode(
['url' => 'https://example.com/path'],
Json::DEFAULT_OPTIONS | JSON_UNESCAPED_SLASHES
);
Итог
Использование Bitrix\Main\Web\Json вместо нативных функций обеспечивает явную обработку ошибок и автоматическую защиту от XSS. Метод validate() позволяет безопасно проверять пользовательский ввод перед декодированием.
Правильная работа с Bitrix\Main\Result для обработки ошибок
Проблема
Многие разработчики до сих пор возвращают булевы значения или массивы из своих методов, игнорируя стандартный класс Bitrix\Main\Result. Это приводит к неединообразной обработке ошибок, усложняет отладку и делает код несовместимым с современным API Битрикс.
Решение
Класс Result предоставляет стандартизированный способ возврата результатов операций с возможностью передачи ошибок и данных.
Базовое использование
use Bitrix\Main\Result;
use Bitrix\Main\Error;
function processOrder(int $orderId): Result
{
$result = new Result();
// Проверка существования заказа
if (!$order = getOrder($orderId)) {
$result->addError(new Error('Заказ не найден', 'ORDER_NOT_FOUND'));
return $result;
}
// Проверка статуса
if ($order['STATUS'] === 'PAID') {
$result->addError(new Error('Заказ уже оплачен', 'ORDER_ALREADY_PAID'));
return $result;
}
// Успешная обработка
$result->setData([
'order_id' => $orderId,
'amount' => $order['PRICE'],
'timestamp' => time()
]);
return $result;
}
Проверка результата
$result = processOrder(123);
if ($result->isSuccess()) {
$data = $result->getData();
echo "Заказ обработан: " . $data['order_id'];
} else {
// Получение всех ошибок
foreach ($result->getErrors() as $error) {
echo $error->getCode() . ': ' . $error->getMessage();
}
// Или только первой ошибки
$error = $result->getError();
echo $error->getMessage();
// Или всех сообщений одним массивом
$messages = $result->getErrorMessages();
}
Добавление множественных ошибок
function validateOrderData(array $data): Result
{
$result = new Result();
$errors = [];
if (empty($data['email'])) {
$errors[] = new Error('Email обязателен', 'EMAIL_REQUIRED');
}
if (empty($data['phone'])) {
$errors[] = new Error('Телефон обязателен', 'PHONE_REQUIRED');
}
if (!empty($errors)) {
$result->addErrors($errors);
}
return $result;
}
Передача ошибок между методами
function createUser(array $userData): Result
{
$result = new Result();
// Валидация
$validationResult = validateUserData($userData);
if (!$validationResult->isSuccess()) {
$result->addErrors($validationResult->getErrors());
return $result;
}
// Создание пользователя
// ...
return $result;
}
Дополнительные данные в ошибках
$error = new Error(
'Недостаточно товара на складе',
'INSUFFICIENT_STOCK',
['available' => 5, 'requested' => 10] // customData
);
// Получение дополнительных данных
$customData = $error->getCustomData();
echo "Доступно: " . $customData['available'];
Итог
Использование Result делает код предсказуемым, упрощает обработку ошибок и соответствует стандартам Битрикс. Все современные модули (Sale, Catalog, CRM) используют этот подход.