Безопасная работа с 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\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(), а для сложных сценариев с цепочкой операций — классы напрямую.
Параллельные HTTP-запросы через асинхронный API HttpClient
При интеграции с внешними сервисами часто требуется выполнить несколько HTTP-запросов. Последовательное выполнение приводит к суммированию времени ожидания каждого запроса. Если три API отвечают по 500мс, общее время составит 1.5 секунды. Класс Bitrix\Main\Web\HttpClient поддерживает асинхронное выполнение запросов через curl_multi, что позволяет выполнять их параллельно.
Для асинхронных запросов используется метод sendAsyncRequest(), который возвращает объект Promise. Promise реализует интерфейс Http\Promise\Promise и поддерживает цепочки обработчиков через метод then().
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Http\Request;
use Bitrix\Main\Web\Uri;
// Важно: для асинхронных запросов требуется CURL
$client = new HttpClient(['useCurl' => true]);
// Список URL для параллельных запросов
$urls = [
'products' => 'https://api.example.com/products',
'categories' => 'https://api.example.com/categories',
'prices' => 'https://api.example.com/prices',
];
$promises = [];
foreach ($urls as $key => $url)
{
// Создаём PSR-7 совместимый Request
$request = new Request('GET', new Uri($url));
// sendAsyncRequest() не блокирует выполнение
$promises[$key] = $client->sendAsyncRequest($request);
}
// wait() блокирует до завершения всех запросов
$responses = $client->wait();
// Обрабатываем результаты
foreach ($promises as $key => $promise)
{
try
{
// wait() на конкретном promise возвращает Response
$response = $promise->wait();
$data[$key] = json_decode((string)$response->getBody(), true);
}
catch (\Bitrix\Main\Web\Http\ClientException $e)
{
// Обработка ошибок сети
$data[$key] = ['error' => $e->getMessage()];
}
}
Promise поддерживает callback-функции для обработки успешных и неуспешных запросов:
$promise = $client->sendAsyncRequest($request);
// Регистрируем обработчики до вызова wait()
$promise->then(
function ($response) {
// Вызывается при успешном ответе
// Можно модифицировать и вернуть response
return $response;
},
function ($exception) {
// Вызывается при ошибке
// Логируем или обрабатываем исключение
return $exception;
}
);
// Запускаем выполнение
$client->wait();
Для POST-запросов с телом используйте Http\FormStream:
use Bitrix\Main\Web\Http\FormStream;
$body = new FormStream(['param1' => 'value1', 'param2' => 'value2']);
$request = new Request('POST', new Uri($url), ['Content-Type' => 'application/x-www-form-urlencoded'], $body);
$promise = $client->sendAsyncRequest($request);
Асинхронный API HttpClient использует curl_multi_exec() под капотом, что обеспечивает истинную параллельность на уровне сетевых операций. Три запроса по 500мс выполнятся примерно за 500мс вместо 1.5 секунд.
Работа с Highload-блоками по имени вместо ID
Продолжаем тему избавления кодовой базы от ID при работе с сущностями Битрикс.
Проблема
Типичный код работы с Highload-блоками содержит жёстко прописанные ID:
<?php
// Антипаттерн: ID зашит в код
$hlblock = HighloadBlockTable::getById(5)->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
ID Highload-блока различается между окружениями: на dev-сервере это 5, на production — 12. При переносе кода приходится менять значения вручную или использовать конфигурационные файлы. Ситуация усугубляется, когда ID разбросаны по десяткам файлов проекта.
Решение
Метод HighloadBlockTable::resolveHighloadblock() принимает символьное имя HL-блока вместо числового ID. Имя задаётся при создании блока и остаётся неизменным при переносе между окружениями.
<?php
use Bitrix\Highloadblock\HighloadBlockTable;
use Bitrix\Main\Loader;
Loader::includeModule('highloadblock');
// Правильно: используем символьное имя
$hlblock = HighloadBlockTable::resolveHighloadblock('Cities');
if ($hlblock !== null) {
$entity = HighloadBlockTable::compileEntity($hlblock);
$entityClass = $entity->getDataClass();
$items = $entityClass::getList([
'filter' => ['=UF_ACTIVE' => 1]
])->fetchAll();
}
Метод compileEntity также поддерживает имя
Символьное имя можно передавать напрямую в compileEntity() — метод внутри вызывает resolveHighloadblock():
<?php
// Компиляция entity по имени — без промежуточных вызовов
$entity = HighloadBlockTable::compileEntity('Cities');
$entityClass = $entity->getDataClass();
$cities = $entityClass::getList()->fetchAll();
Константы для имён HL-блоков
Для централизованного управления именами создайте класс с константами:
<?php
namespace App\Reference;
class HLBlock
{
public const CITIES = 'Cities';
public const REGIONS = 'Regions';
public const COLORS = 'ProductColors';
public const SIZES = 'ProductSizes';
}
Использование в коде:
<?php
use App\Reference\HLBlock;
$entity = HighloadBlockTable::compileEntity(HLBlock::CITIES);
$entityClass = $entity->getDataClass();
При таком подходе IDE подсказывает доступные HL-блоки, а опечатки выявляются на этапе статического анализа.
Встроенное кеширование
Метод resolveHighloadblock() кеширует результат запроса на 24 часа. Повторные вызовы с тем же именем не обращаются к базе данных:
<?php
// Первый вызов — запрос к БД, результат кешируется
$hlblock1 = HighloadBlockTable::resolveHighloadblock('Cities');
// Повторный вызов — данные из кеша ORM
$hlblock2 = HighloadBlockTable::resolveHighloadblock('Cities');
Валидация имени
Метод проверяет корректность символьного имени регулярным выражением /^[a-z0-9_]+$/i. При несуществующем или некорректном имени возвращается null:
<?php
$hlblock = HighloadBlockTable::resolveHighloadblock('NonExistent');
// $hlblock === null
Итог
Замените числовые ID на символьные имена HL-блоков через resolveHighloadblock(). Код станет переносимым между окружениями без ручных правок, а централизованные константы обеспечат контроль над используемыми справочниками.