#d7

Советы с тегом "d7"

8 советов

Как удаляются связи OneToMany и ManyToMany в D7 ORM

Как удаляются связи OneToMany и ManyToMany в D7 ORM

Проблема/контекст

В D7-связях удаление часто понимают слишком упрощенно: если сущности связаны, значит ядро само "разрулит" каскад. Но в исходниках ORM поведение у OneToMany и ManyToMany принципиально разное, и ошибка в ожиданиях быстро приводит либо к лишнему удалению, либо к зависшим данным.

Ключевая точка здесь Bitrix\Main\ORM\Objectify\EntityObject::delete(). Для OneToMany метод смотрит на getCascadeDeletePolicy() и либо удаляет дочерние объекты, либо снимает ссылку через sysRemoveAllFromCollection(). Для ManyToMany логика иная: при удалении объекта ядро всегда очищает таблицу-посредник, а сами связанные сущности не удаляет. Это напрямую следует из связки EntityObject::delete(), sysRemoveAllFromCollection() и sysSaveRelations().

Решение с кодом

Если дочерняя запись принадлежит только одному владельцу, используйте OneToMany и явно задавайте политику удаления:

        <?php
use Bitrix\Main\ORM\Fields\Relations\CascadePolicy;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;

final class OrderTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getMap(): array
    {
        return [
            // При удалении заказа удалятся и его позиции
            (new OneToMany('ITEMS', OrderItemTable::class, 'ORDER'))
                ->configureCascadeDeletePolicy(CascadePolicy::FOLLOW),
        ];
    }
}

    

Если FOLLOW не указан, у OneToMany по умолчанию работает SET_NULL: ядро не удаляет дочерние строки, а отвязывает их от родителя. Это удобно для журналов, уведомлений и других записей, которые могут жить дольше основной сущности.

Для общих справочников и тегов используйте ManyToMany, но помните реальное поведение удаления:

        <?php
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;

final class ArticleTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getMap(): array
    {
        return [
            // При удалении статьи ядро очистит только b_article_tag
            (new ManyToMany('TAGS', TagTable::class))
                ->configureTableName('b_article_tag'),
        ];
    }
}

    

После $article->delete() будут удалены строки из b_article_tag, потому что sysSaveRelations() удаляет записи посредника через data class медиаторной сущности. Сами теги останутся. Поэтому ManyToMany подходит там, где связь должна исчезнуть, а партнерская сущность продолжает использоваться в системе. Если же нужно удалять и "осиротевшие" записи, не полагайтесь на ManyToMany: описывайте таблицу связи явно или запускайте отдельный сервис очистки после удаления основной сущности.

Итог

Для OneToMany в D7 важно выбирать политику FOLLOW или SET_NULL осознанно. Для ManyToMany безопасно рассчитывать только на очистку таблицы связей, а не на удаление связанных объектов.

Как защитить агент импорта от двойного запуска с помощью Connection::lock()

Как защитить агент импорта от двойного запуска с помощью Connection::lock()

Проблема/контекст

Для агентов, cron-задач и CLI-команд в Битрикс типовая ошибка одна: процесс стартует второй раз, пока первая копия еще держит критическую секцию. В результате появляются дубли импорта, повторные списания, гонки при обновлении сущностей и нестабильные ошибки, которые сложно воспроизвести.

В ядре для этого уже есть встроенный механизм. В Bitrix\Main\DB\MysqlCommonConnection::lock() платформа переводит пул в master only, вызывает SELECT GET_LOCK(...), а имя блокировки нормализует через CMain::GetServerUniqID(). Это важная деталь: блокировка берется не на уровне PHP-процесса, а на уровне текущего соединения с основной БД, поэтому она подходит именно для межпроцессной синхронизации.

Решение с кодом

Если задача должна выполняться строго в одном экземпляре, ставьте блокировку в самом начале и всегда снимайте ее в finally. Такой подход соответствует и самому ядру: Bitrix\Main\Messenger\Internals\Storage\Db\DbStorage не читает очередь без Application::getConnection()->lock('queueLock'), а в main/classes/general/usertype.php изменение UTS-таблиц обернуто в блокировку uf_add_*.

        <?php
declare(strict_types=1);

use Bitrix\Main\Application;
use Bitrix\Main\Diag\Logger;
use Bitrix\Main\Loader;

final class CatalogImportAgent
{
    public static function run(): string
    {
        $connection = Application::getConnection();
        $lockName = 'catalog_import';

        // Вторая копия не ждет: если импорт уже идет, просто выходим
        if (!$connection->lock($lockName, 0))
        {
            return CatalogImportAgent::class . '::run();';
        }

        try
        {
            Loader::includeModule('iblock');
            (new ImportService())->sync();
        }
        catch (\Throwable $e)
        {
            Logger::create('catalog_import')?->error($e->getMessage());
        }
        finally
        {
            // Освобождаем lock даже при исключении
            $connection->unlock($lockName);
        }

        return CatalogImportAgent::class . '::run();';
    }
}

    

Практический вывод здесь двойной. Во-первых, named lock не заменяет транзакцию: он защищает вход в критическую секцию, а не целостность отдельных SQL-операций. Во-вторых, имя должно быть стабильным и описывать конкретный ресурс, например catalog_import, prices_sync или agent_invoice_export. Если использовать случайные ключи, механизм потеряет смысл. Для долгих задач имеет смысл выбирать ненулевой таймаут, но для агентов и cron-скриптов чаще полезнее мгновенно завершить повторный запуск и дождаться следующего окна.

Итог

Connection::lock() в Битрикс уже решает задачу одиночного запуска без самодельных файлов-флажков и временных таблиц. Если процесс нельзя выполнять параллельно, ставьте named lock до начала работы и снимайте его гарантированно через finally.

Как маркировать предупреждения в Bitrix\Sale

Как маркировать предупреждения в Bitrix\Sale

Проблема/контекст

В sale не каждая проблемная ситуация должна ронять сохранение заказа. Часто интеграция с доставкой, кассой или внутренней проверкой находит состояние, которое нужно показать оператору, но не превращать в фатальную ошибку. В ядре для этого есть не только поля MARKED и REASON_MARKED, но и отдельный механизм Bitrix\Sale\EntityMarker: он берет warnings из Bitrix\Sale\Result, создает marker-записи, записывает последнюю причину в заказ и помечает оплату или отгрузку как проблемную.

Практически это значит: если вы уже возвращаете Result с предупреждениями, не дублируйте еще один собственный реестр проблем. Используйте штатный контур Sale.

Решение с кодом

Ключевой момент в ядре такой: EntityMarker::addMarker() работает именно с warnings, а не с обычными errors. Поэтому для нефатального кейса нужен Bitrix\Sale\ResultWarning.

        <?php
declare(strict_types=1);

use Bitrix\Main\Loader;
use Bitrix\Sale\EntityMarker;
use Bitrix\Sale\Order;
use Bitrix\Sale\Result;
use Bitrix\Sale\ResultWarning;
use Bitrix\Sale\Shipment;

Loader::includeModule('sale');

function markShipmentIntegrationWarning(Order $order, Shipment $shipment): void
{
    $result = new Result();

    // Нефатальная проблема: заказ сохранять можно, но отгрузку нужно подсветить оператору
    $result->addWarning(new ResultWarning(
        'Не удалось подтвердить трек-номер во внешней службе доставки',
        'SHIPMENT_TRACKING_SYNC_WARNING'
    ));

    EntityMarker::addMarker($order, $shipment, $result);
    EntityMarker::saveMarkers($order);

    $saveResult = $order->save();
    if (!$saveResult->isSuccess())
    {
        throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
    }
}

    

После этого в заказ попадет REASON_MARKED, у отгрузки выставится MARKED = 'Y', а сам marker сохранится в таблице маркеров. Это важнее простого $shipment->setField('MARKED', 'Y'): в ядре сохраняются код проблемы, тип маркера (AUTO или MANUAL) и статус успешности исправления.

Warning в заказе

Когда причина уже устранена, снимайте проблему не только с поля, но и с marker-записи. Для этого можно удалить маркеры сущности и пересчитать состояние заказа через refreshMarkers().

        <?php
declare(strict_types=1);

use Bitrix\Sale\EntityMarker;
use Bitrix\Sale\Order;
use Bitrix\Sale\Shipment;

function clearShipmentMarkers(Order $order, Shipment $shipment): void
{
    // deleteByEntity() работает только для уже сохраненной сущности с ID
    EntityMarker::deleteByEntity($shipment);
    EntityMarker::refreshMarkers($order);

    $saveResult = $order->save();
    if (!$saveResult->isSuccess())
    {
        throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
    }
}

    

Итог

EntityMarker в sale нужен не для декоративного флага, а для нормального жизненного цикла предупреждений. Если проблема не фатальна, но должна остаться в истории и интерфейсе заказа, сохраняйте ее как marker, а после исправления очищайте через API маркеров, а не только через поле MARKED.

Получение DETAIL_PAGE_URL для элемента инфоблока в D7

Получение DETAIL_PAGE_URL для элемента инфоблока в D7

Проблема/контекст

В новом ядре D7 (таблица Bitrix\Iblock\ElementTable) нет готового поля DETAIL_PAGE_URL. Это связано с тем, что ссылка на детальную страницу — это динамический шаблон (например, /catalog/#SECTION_CODE#/#ELEMENT_CODE#/), который хранится в настройках инфоблока и требует подстановки реальных данных элемента для вычисления.

Решение с кодом

Чтобы получить корректную ссылку, необходимо выбрать данные элемента вместе с шаблоном URL из связанной таблицы инфоблока. Рекомендуется использовать Element API (если у инфоблока задан API_CODE), но способ также работает и с базовым ElementTable.

        <?php
declare(strict_types=1);

use Bitrix\Iblock\Elements\ElementCatalogTable; // Где 'Catalog' — это API_CODE вашего инфоблока
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

$elementId = 123;

// Если у инфоблока нет API_CODE, используйте \Bitrix\Iblock\ElementTable
$element = ElementCatalogTable::getList([
    'select' => [
        'ID',
        'CODE',
        'IBLOCK_SECTION_ID',
        // Получаем шаблон URL из настроек инфоблока через связь IBLOCK
        'DETAIL_PAGE_URL_TEMPLATE' => 'IBLOCK.DETAIL_PAGE_URL'
    ],
    'filter' => ['=ID' => $elementId]
])->fetch();

if ($element)
{
    // Метод ReplaceDetailUrl заменит плейсхолдеры типа #ID#, #CODE# на реальные значения из массива $element
    $detailUrl = \CIBlock::ReplaceDetailUrl(
        url: $element['DETAIL_PAGE_URL_TEMPLATE'],
        arr: $element,
        server_name: true,
        arrType: 'E' // Тип "E" означает Element
    );

    echo $detailUrl;
}

    

Пример с использованием fetchObject()

Если вы предпочитаете работать с объектами (EO_Element), данные для замены масок нужно подготовить через метод collectValues():

        $elementObject = ElementCatalogTable::getList([
    'select' => ['ID', 'CODE', 'IBLOCK_SECTION_ID', 'IBLOCK.DETAIL_PAGE_URL'],
    'filter' => ['=ID' => $elementId]
])->fetchObject();

if ($elementObject)
{
    // ReplaceDetailUrl ожидает массив, поэтому используем collectValues()
    $detailUrl = \CIBlock::ReplaceDetailUrl(
        url: $elementObject->getIblock()->getDetailPageUrl(),
        arr: $elementObject->collectValues(),
        server_name: true,
        arrType: 'E'
    );
}

    

Параметры метода ReplaceDetailUrl

  1. $url (string) — Шаблон URL из настроек инфоблока (например, /catalog/#SECTION_CODE#/#ELEMENT_CODE#/).
  2. $arr (array) — Массив данных элемента. Ключи должны совпадать с масками (ID для #ID#, CODE для #CODE# и т.д.).
  3. $server_name (bool) — Если true, метод подставит SITE_DIR и домен сайта. Полезно для генерации полных ссылок (в письмах или RSS).
  4. $arrType (string) — Тип сущности: "E" для элементов, "S" для разделов. Это определяет, какие маски будут обрабатываться (например, #SECTION_CODE_PATH#).

Практические детали:

  • Обязательные поля: В select запроса getList нужно обязательно включать все поля, которые используются в шаблоне ссылки (обычно это ID, CODE, IBLOCK_SECTION_ID). Если в шаблоне есть #SECTION_CODE#, вам придется добавить в select связь с секцией, например 'SECTION_CODE' => 'IBLOCK_SECTION.CODE'.
  • Element API vs ElementTable: Использование ElementCatalogTable предпочтительнее для новых проектов, так как это дает типизацию и упрощенную работу со свойствами. Но если вы пишете универсальный код или API_CODE не задан, \Bitrix\Iblock\ElementTable работает по тому же принципу.
  • Тип сущности: При вызове ReplaceDetailUrl последний параметр 'E' указывает, что мы работаем с элементом. Для разделов используется 'S'.

Итог

Для получения ссылки в D7 используйте выборку шаблона через связь IBLOCK.DETAIL_PAGE_URL и стандартный метод \CIBlock::ReplaceDetailUrl для подстановки данных. Это наиболее производительный и правильный способ в рамках Bitrix Framework.

Обновление свойств и количества товаров в корзине Bitrix через Sale API

Обновление свойств и количества товаров в корзине Bitrix через Sale API

В современной разработке на платформе 1С-Битрикс любые манипуляции с корзиной покупателя должны выполняться исключительно через объектно-ориентированное API модуля «Интернет-магазин» (sale). Распространенной ошибкой среди начинающих разработчиков является попытка прямого изменения записей в таблице базы данных b_sale_basket или использование устаревших методов глобального класса CSaleBasket. Такой подход крайне опасен, поскольку он не инициирует сложную цепочку внутренних бизнес-процессов ядра: автоматический пересчет правил корзины, применение скидок, расчет налогов и актуализацию стоимости доставки. В результате данные в корзине становятся некорректными, что ведет к финансовым потерям и сбоям при оформлении заказа.

Для правильного и безопасного обновления данных необходимо работать с высокоуровневыми объектами корзины. Ключевым инструментом в данном контексте выступает класс Bitrix\Sale\Basket. Загрузка объектов обычно осуществляется через статический метод loadItemsForFUser, который принимает идентификатор владельца корзины (FUser ID) и идентификатор сайта. Это позволяет получить актуальный объект корзины со всеми вложенными элементами, даже если заказ еще не начал оформляться. После того как корзина загружена, вы можете получить доступ к конкретному элементу Bitrix\Sale\BasketItem, используя его внутренний идентификатор или выполнив поиск по PRODUCT_ID.

Объект BasketItem предоставляет набор методов для управления полями записи. Например, метод setField('QUANTITY', $value) позволяет изменить количество товара, а работа с кастомными характеристиками товара осуществляется через специальную коллекцию свойств, доступную через метод getPropertyCollection(). Это критически важно для товаров с торговыми предложениями или индивидуальными параметрами, которые выбирает пользователь.

Пример реализации кода для обновления параметров товара в корзине:

        use Bitrix\Main\Loader;
use Bitrix\Sale\Basket;
use Bitrix\Sale\Fuser;
use Bitrix\Main\Context;

// Обязательная проверка подключения модуля sale
if (Loader::includeModule('sale')) {
    // Получаем внутренний идентификатор корзины текущего пользователя
    $fUserId = Fuser::getId();
    $siteId = Context::getCurrent()->getSite();

    // Загружаем объект корзины со всеми товарами
    $basket = Basket::loadItemsForFUser($fUserId, $siteId);

    // Поиск конкретного элемента корзины по ID товара (PRODUCT_ID)
    // В реальных задачах ID часто передается через AJAX-запрос
    $productId = 201;
    $basketItems = $basket->getExistsItems('catalog', $productId, null); // здесь null - если нам не важны свойства товара, например, размер

    if (!empty($basketItems)) {
        // Берем первый найденный элемент
        $basketItem = current($basketItems);
			
        // 1. Изменение количества товара с автоматической проверкой доступности
        $basketItem->setField('QUANTITY', 5);

        // 2. Управление коллекцией свойств товара (например, цвет или размер)
        $propertyCollection = $basketItem->getPropertyCollection();
        
        // Метод setProperty позволяет массово обновить или добавить свойства
        $propertyCollection->setProperty([
            [
                'NAME' => 'Размер',
                'CODE' => 'SIZE',
                'VALUE' => 'XL',
                'SORT' => 100,
            ],
        ]);

        // 3. Вызов метода save() сохраняет изменения в БД и запускает пересчеты
        $saveResult = $basket->save();

        if (!$saveResult->isSuccess()) {
            // В случае ошибки возвращаем массив описаний проблем
            $errors = $saveResult->getErrorMessages();
            // Логирование ошибок или вывод пользователю
        }
    }
}

    

Важно учитывать контекст выполнения: если объект корзины уже является частью объекта заказа (Bitrix\Sale\Order), то вызывать метод save() непосредственно у корзины не рекомендуется. В этом случае правильнее вызвать сохранение всего заказа через $order->save(). Это гарантирует, что итоговая сумма заказа, налоги и все связанные сущности (отгрузки, оплаты) будут синхронизированы и пересчитаны в рамках одной транзакции.

Использование объектной модели BasketItem — это стандарт качественной разработки, который обеспечивает масштабируемость и стабильность вашего решения при любых обновлениях платформы.

Работа с изображениями через Bitrix\Main\File\Image

Работа с изображениями через Bitrix\Main\File\Image

Разработчики часто используют CFile::ResizeImageGet() для изменения размеров изображений, не подозревая, что эта функция является обёрткой над современным D7 API. Классы Bitrix\Main\File\Image предоставляют прямой доступ к операциям с изображениями, что даёт больше контроля и гибкости.

Основные операции с Image

        use Bitrix\Main\File\Image;
use Bitrix\Main\File\Image\Rectangle;
use Bitrix\Main\File\Image\Mask;

$image = new Image('/path/to/image.jpg');
$image->load();

// Получение информации о изображении
$info = $image->getInfo();
echo $info->getWidth() . 'x' . $info->getHeight(); // размеры
echo $info->getMime(); // MIME-тип
echo $info->getFormat(); // Image::FORMAT_JPEG, FORMAT_PNG, etc.

// Изменение размера (пропорционально)
$source = $image->getDimensions();
$destination = new Rectangle(800, 600);
$source->resize($destination, Image::RESIZE_PROPORTIONAL);
$image->resize($source, $destination);

// Сохранение с качеством 85%
$image->save(85);

    

Аналог unsharpmask из CFile::ResizeImageGet

        // CFile::ResizeImageGet по умолчанию применяет sharpen с precision=15
$mask = Mask::createSharpen(15);
$image->filter($mask);

    

Режимы изменения размера

        // RESIZE_PROPORTIONAL - сохраняет пропорции, вписывает в указанный прямоугольник
$source->resize($destination, Image::RESIZE_PROPORTIONAL);

// RESIZE_EXACT - кадрирует изображение по центру до точных размеров
$source->resize($destination, Image::RESIZE_EXACT);

// RESIZE_PROPORTIONAL_ALT - учитывает ориентацию (портрет/ландшафт)
$source->resize($destination, Image::RESIZE_PROPORTIONAL_ALT);

    

Расширенные возможности

        // Поворот и отражение
$image->rotate(90); // поворот на 90°
$image->flipHorizontal(); // зеркальное отражение
$image->autoRotate($exifOrientation); // автокоррекция по EXIF

// Размытие
$image->blur(10); // sigma от 1 до 100

// Водяной знак (изображение)
use Bitrix\Main\File\Image\ImageWatermark;

$watermark = new ImageWatermark('/path/to/watermark.png');
$watermark->setAlignment('right', 'bottom')
    ->setPadding(20)
    ->setAlpha(0.7);
$image->drawWatermark($watermark);

// Сохранение в другой формат
$image->saveAs('/path/to/output.webp', 85, Image::FORMAT_WEBP);

    

Выбор движка: GD или Imagick

        // По умолчанию используется GD
// Для Imagick зарегистрируйте сервис:
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\File\Image\Imagick;

$serviceLocator = ServiceLocator::getInstance();
$serviceLocator->registerByCreator('main.imageEngine', fn() => new Imagick());

    

Классы Bitrix\Main\File\Image полностью покрывают функциональность CFile::ResizeImageGet(), включая proportional resize, exact crop, sharpen-фильтр и водяные знаки. Для типовых задач resizing проще использовать CFile::ResizeImageGet(), а для сложных сценариев с цепочкой операций — классы напрямую.

InsertIgnore стратегия для безопасной вставки без дублирования

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 через 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 — защита от XSS
  • JSON_UNESCAPED_UNICODE — корректная работа с кириллицей
  • JSON_INVALID_UTF8_SUBSTITUTE — замена невалидных символов UTF-8
  • JSON_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() позволяет безопасно проверять пользовательский ввод перед декодированием.

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

AI Домовой История

0 / 25

Привет! Я помогу с вопросами по 1С-Битрикс.

Спрашивай про D7, ORM, компоненты или события.

Требуется авторизация

Войдите или зарегистрируйтесь, чтобы задавать вопросы AI-ассистенту.

Войти