#api

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

5 советов

Получение 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.

Параллельные HTTP-запросы через асинхронный API HttpClient

Параллельные 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 секунд.

Выбор между send и sendImmediate для почтовых событий

Выбор между send и sendImmediate для почтовых событий

При отправке почтовых событий в 1С-Битрикс разработчики часто используют старый API CEvent::Send(), не зная о современном классе Bitrix\Main\Mail\Event и его двух принципиально разных методах отправки. Неправильный выбор метода приводит к блокировке выполнения скрипта при проблемах с почтовым сервером или избыточной нагрузке на базу данных.

Метод send(): отложенная отправка

Метод send() добавляет событие в очередь отправки (таблица b_event). Агент Битрикс обработает событие позже:

        use Bitrix\Main\Mail\Event;

// Добавление в очередь с автоматическим выбором шаблона
$result = Event::send([
    'EVENT_NAME' => 'ORDER_CONFIRM',
    'LID' => 's1',
    'C_FIELDS' => [
        'ORDER_ID' => $orderId,
        'USER_EMAIL' => $email,
        'ORDER_LIST' => $orderHtml,
    ],
]);

// Проверка результата
if ($result->isSuccess()) {
    $eventId = $result->getId();
} else {
    $errors = $result->getErrorMessages();
}

    

Метод send() возвращает объект Result с методами проверки успешности и получения ошибок, что упрощает обработку по сравнению со старым API.

Отправка конкретного почтового шаблона

Для отправки определённого шаблона используйте параметр MESSAGE_ID:

        // Отправка конкретного шаблона (ID из таблицы b_event_message)
$result = Event::send([
    'EVENT_NAME' => 'ORDER_CONFIRM',
    'LID' => 's1',
    'MESSAGE_ID' => 5, // ID конкретного шаблона
    'C_FIELDS' => [
        'ORDER_ID' => $orderId,
        'USER_EMAIL' => $email,
    ],
]);

    

Без MESSAGE_ID система выберет все активные шаблоны по EVENT_NAME и LID. С MESSAGE_ID будет использован только указанный шаблон, даже если есть другие для этого типа события.

Метод sendImmediate(): немедленная отправка

Метод sendImmediate() отправляет письмо синхронно, минуя очередь:

        use Bitrix\Main\Mail\Event;

// Немедленная отправка с конкретным шаблоном
$sendResult = Event::sendImmediate([
    'EVENT_NAME' => 'CRITICAL_ALERT',
    'LID' => 's1',
    'MESSAGE_ID' => 12, // Опционально: конкретный шаблон
    'C_FIELDS' => [
        'MESSAGE' => 'Критическая ошибка в системе',
        'TIME' => date('Y-m-d H:i:s'),
    ],
]);

// Результат - строковая константа
if ($sendResult === Event::SEND_RESULT_SUCCESS) {
    // Отправлено успешно
} elseif ($sendResult === Event::SEND_RESULT_ERROR) {
    // Ошибка отправки
} elseif ($sendResult === Event::SEND_RESULT_TEMPLATE_NOT_FOUND) {
    // Шаблон не найден
}

    

Используйте sendImmediate() для критических уведомлений в фоновых задачах (агенты, cron), когда письмо должно уйти немедленно без сохранения в очередь.

Метод sendImmediate() возвращает строку, в отличие от send(), а не объект Result.

Обработка вложений

Оба метода поддерживают массив вложений в параметре FILE:

        $result = Event::send([
    'EVENT_NAME' => 'DOCUMENT_READY',
    'LID' => ['s1', 's2'], // Множественные сайты
    'C_FIELDS' => ['DOC_NUMBER' => '123'],
    'FILE' => [
        456, // ID из b_file (не копируется)
        '/path/to/new.pdf', // Новый файл (будет скопирован)
    ],
]);

    

Система автоматически определяет тип вложения: числовые значения считаются ID файлов, строки - путями к файлам для копирования.

Итог

Используйте send() для пользовательских сценариев и sendImmediate() для фоновых задач. Параметр MESSAGE_ID позволяет точно контролировать, какой шаблон будет использован.

Безопасная работа с 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() позволяет безопасно проверять пользовательский ввод перед декодированием.

Правильная работа с Bitrix\Main\Result для обработки ошибок

Правильная работа с 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) используют этот подход.

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