AJAX в компонентах Битрикс: правильный способ без костылей

Отдельные PHP-файлы «для ajax», самописный роутинг и echo json_encode без CSRF — частый путь к дырявым интеграциям и поломке композита. В Битрикс есть штатные варианты: параметр AJAX_MODE у компонента, ручная ветка isAjaxRequest() и BX.ajax.runComponentAction на Engine. Ниже — когда какой способ брать и как не сломать кеш.

Связанные темы: Web\Json, Page\Asset, Application / Context / Request, Engine\Controller: AJAX, runAction и типичные ошибки, REST API в D7.

Способ 1: штатный AJAX_MODE

Для пагинации, сортировки и простых фильтров достаточно включить режим у компонента — ядро перерисует блок без полной перезагрузки:

$APPLICATION->IncludeComponent(
    'company:catalog.list',
    '',
    [
        'AJAX_MODE'         => 'Y',
        'AJAX_OPTION_JUMP'  => 'N',
        'AJAX_OPTION_STYLE' => 'Y',
        'AJAX_OPTION_HISTORY' => 'N',
    ]
);

AJAX_OPTION_STYLE=Y просит подтянуть CSS/JS фрагмента. Имеет смысл заранее зарегистрировать стили через Asset на полной отрисовке, а не надеяться, что всё приедет только с ajax-ответом.

Способ 2: ручной AJAX в компоненте

Когда нужен JSON (load more, избранное, пересчёт корзины) — обрабатывайте запрос в том же компоненте:

// class.php / component.php
use Bitrix\Main\Web\Json;

if ($this->request->isAjaxRequest()) {
    $action = (string)$this->request->get('action');

    if ($action === 'load_more') {
        if (!check_bitrix_sessid()) {
            $this->sendJson(['error' => 'invalid_sessid'], 403);
        }

        $page  = max(1, (int)$this->request->get('page'));
        $items = $this->loadItems($page);

        $this->sendJson([
            'items'   => $items,
            'hasMore' => count($items) === (int)$this->arParams['PAGE_SIZE'],
        ]);
    }
}

protected function sendJson(array $data, int $status = 200): void
{
    $response = \Bitrix\Main\Context::getCurrent()->getResponse();
    $response->setStatus($status);
    $response->addHeader('Content-Type', 'application/json; charset=UTF-8');
    $response->flush(Json::encode($data));
    die();
}

Не используйте «голый» json_encode без проверки ошибок — берите Bitrix\Main\Web\Json. Не отдавайте HTML ошибки PHP внутри JSON: иначе фронт получит невалидный ответ.

JavaScript: fetch + sessid

async function loadMore(page) {
    const body = new URLSearchParams({
        action: 'load_more',
        page: String(page),
        sessid: BX.message('bitrix_sessid'),
    });

    const response = await fetch(window.location.pathname + window.location.search, {
        method: 'POST',
        headers: {
            'X-Bitrix-Csrf-Token': BX.message('bitrix_sessid'),
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
            'X-Requested-With': 'XMLHttpRequest',
        },
        body,
        credentials: 'same-origin',
    });

    if (!response.ok) {
        throw new Error('HTTP ' + response.status);
    }

    const data = await response.json();
    renderItems(data.items);
}

Способ 3: BX.ajax.runComponentAction

Для контроллеров Engine у компонента — штатный JS-хелпер. CSRF и формат ответа закрывает ядро:

BX.ajax.runComponentAction(
    'company:catalog.list',
    'loadMore',
    {
        mode: 'class',
        data: { page: 2 },
    }
).then(function (response) {
    console.log(response.data);
}).catch(function (err) {
    console.error(err.errors || err);
});

В PHP action-метод должен заканчиваться на Action, prefilters — Authentication/Csrf/HttpMethod по необходимости. Каркас контроллеров и маршрутов — в гайде по REST API D7.

Если логика не привязана к конкретному компоненту (сохранение сущности модуля, общий виджет, админский ajax), современнее вынести её в модульный Bitrix\Main\Engine\Controller и вызывать через BX.ajax.runAction — см. Engine\Controller: AJAX, runAction и типичные ошибки. runComponentAction оставляйте для действий, которым нужен контекст компонента (шаблон, параметры, кеш компонента).

Защита и типичные ошибки

  • Всегда проверяйте check_bitrix_sessid() (или prefilter Csrf) на изменяющих действиях.
  • Не открывайте публичные /ajax/*.php без проверки прав и sessid — это отдельная поверхность атаки.
  • Не кешируйте персональный JSON в файловом кеше компонента без учёта пользователя.
  • Регистрируйте CSS/JS через Asset на полном хите; ajax только меняет разметку/данные.
  • Ответ — через Json::encode и явный Content-Type: application/json.

Вывод

Для пагинации и фильтров начинайте с AJAX_MODE. Для JSON-контракта компонента — ветка isAjaxRequest() или runComponentAction. Для модульного AJAX без привязки к компоненту — Engine\Controller: AJAX, runAction и типичные ошибки и BX.ajax.runAction. Отдельные «левые» PHP-эндпоинты без Engine оставляйте только для легаси. Сериализацию держите на Web\Json, ресурсы фронта — на Page\Asset.