Перейти к содержимому

AJAX-запрос - контроллер, свой файл и ответ в JSON

Принимаем асинхронный запрос на стороне сайта и возвращаем ответ, который браузер сможет разобрать.

Решение

Принимаем запрос контроллером:

namespace Local\Controller;
use Bitrix\Main\Engine\Controller;
class Feedback extends Controller
{
public function saveAction(string $name, string $phone): array
{
// параметры приходят типизированными аргументами, разбирать их не нужно
return ['status' => 'ok', 'name' => $name]; // ответ уедет в формате JSON
// исключение внутри действия превратится в ответ с описанием ошибки
}
public function configureActions(): array
{
// здесь снимают проверку подписи или добавляют свои правила доступа
return ['save' => ['prefilters' => []]]; // пустой список снимает штатные проверки
}
}

Контроллер сам разбирает параметры, проверяет подпись запроса и упаковывает ответ. Возвращённый массив уходит в браузер как готовый JSON, печатать его самому не нужно.

Зовём контроллер со стороны браузера:

BX.ajax.runAction('local:feedback.feedback.save', {
data: { name: 'Иван', phone: '+7 900 000-00-00' },
}).then((response) => {
console.log(response.data); // здесь лежит то, что вернул метод
});

Имя действия собирается из пространства имён, класса и метода. Ошибка в нём даёт отказ ещё до выполнения кода, и в ответе приходит описание с именем ненайденного действия.

Обходимся своим файлом, когда контроллер избыточен:

/local/ajax/counter.php
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
header('Content-Type: application/json; charset=UTF-8');
echo json_encode(['count' => 42], JSON_UNESCAPED_UNICODE);
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_after.php';

Облегчённое подключение даёт доступ к базе и к API без сборки страницы. Полный пролог публичной части здесь лишний: он собирает шаблон сайта, которого в ответе всё равно не будет.

Включаем режим обмена у компонента:

'AJAX_MODE' => 'Y',
'AJAX_OPTION_HISTORY' => 'N',
'AJAX_OPTION_JUMP' => 'N', // не прокручивать страницу к компоненту

В этом режиме компонент отдаёт только свою часть страницы, а не документ целиком. Запрос при этом остаётся обычным обращением к той же странице, и кэширование работает как всегда.

Чистота ответа важнее его содержимого. Предупреждение PHP, пробел перед открывающим тегом в подключённом файле или забытая отладочная печать попадают в ответ раньше данных и ломают его разбор. Поэтому на боевом сервере вывод ошибок в ответ выключают, а отладочные сообщения пишут в файл.

Выбор точки входа определяется ценой запроса. Контроллер уместен там, где есть логика, права и структурированный ответ; свой файл - там, где нужен один счётчик или короткая проверка; режим обмена у компонента - там, где обновляется готовый кусок страницы. Смешивать эти способы в одном месте кода не стоит: через полгода никто не вспомнит, почему один вызов ушёл в контроллер, а соседний в файл.

Точка обмена доступна каждому, кто знает её адрес. Проверку прав в ней пишут явно: контроллер это умеет через свои правила, а свой файл - только руками. Отключение проверки прав ради удобства открывает данные всему интернету.

Типичные проблемы

В ответ приходит вся страница сайта.

Запрос ушёл на обычную страницу, а не в точку обмена. Компонент в режиме обмена и контроллер отдают только свою часть.

Ответ не разбирается, хотя данные верные.

В него попал посторонний вывод: предупреждение PHP, пробел или отладочная печать. Ответ должен содержать только данные.

Контроллер отвечает «действие не найдено».

Ошибка в имени действия или файл класса лежит не там, где его ищет автозагрузка.

В обработчике недоступны данные пользователя.

Ядро не подключено или подключено слишком облегчённо. Без пролога платформа не знает ни о сессии, ни о правах.

Запрос выполняется секунду вместо десятков миллисекунд.

Подключён полный пролог публичной части. Он собирает шаблон сайта, который в ответе не нужен.

Частые вопросы

Как отправить файл асинхронным запросом?

Обычной формой с данными в двоичном виде: контроллер получит файл в аргументе. Кодировать его в текст не нужно, и на больших файлах это только вредит.

Почему результат запроса не кэшируется?

Кэш компонента работает и в режиме обмена, но свой файл-обработчик не кэшируется ничем. Кэширование в нём пишут руками через кэш данных.

Можно ли обращаться к файлу инициализации напрямую?

Нет, это файл подключения, а не точка входа. Свой обработчик кладут отдельным файлом и подключают в нём ядро.

Как вернуть ошибку из контроллера?

Добавлением ошибки в коллекцию ошибок контроллера: она приходит в ответе отдельным полем. Печатать текст ошибки вручную не нужно.

Смежное

Первоисточник