Play
online · 30 мин
← все статьи
Постмортемы 23 мин чтения 12.05.2026

Bitrix FormOnlineComponent actionStart срабатывает при загрузке страницы: разбор и решение

ActionStart в Bitrix FormOnlineComponent срабатывает при загрузке страницы, а не по AJAX — это ломает логику работы. Разбираем, почему Component Engine 2.0 запускает метод раньше времени, и как это исправить с помощью правильной настройки контроллера.
TL;DR
actionStart вызывается при загрузке страницы, а не только при AJAX Причина — отсутствие явной конфигурации в configureActions Решение: настроить prefilters и postfilters в configureActions Проверять isAjaxRequest() в executeComponent Не использовать action-методы для логики, идущей из executeComponent

TL;DR

Представьте, вы нажали кнопку вызова лифта, а лифт приехал и сразу поехал на ваш этаж, даже не дождавшись, пока вы войдёте. Именно так ведёт себя actionStart в FormOnlineComponent: он срабатывает при первой же загрузке компонента на странице, хотя должен ждать AJAX-команды. Разница между ожидаемым и реальным поведением — как между дверью, открывающейся только по звонку, и дверью, которая распахивается сама, едва вы на неё посмотрели. Всё дело в том, что Component Engine 2.0 по умолчанию воспринимает любой запрос к компоненту (включая обычную загрузку страницы) как потенциальный вызов action-метода, если его явно не ограничить.

Если бы вы поставили обычный замок (старый компонент на cBitrixComponent::Add), дверь бы не открывалась без ключа — action-методы не вызывались бы при загрузке. Но Component Engine 2.0,это автоматическая дверь с датчиком движения: он видит любой запрос (GET или POST) и, если находит метод с префиксом action, запускает его. Разница между старым и новым подходом,как между ручным переключателем и автоматическим реле. Когда вы не настраиваете configureActions, Engine считает, что все action* методы должны вызываться на любой запрос, включая простую загрузку страницы через executeComponent.

Чтобы дверь открывалась только по звонку, нужен фильтр. executeComponent,это холл здания, куда заходят все посетители, а configureActions,швейцар, решающий, кого пустить в кабинет. Если не дать швейцару инструкций, он будет пускать всех подряд, включая курьеров (обычные загрузки страницы). Решение,добавить в configureActions явное ограничение: указать, что actionStart принимает только AJAX-запросы с конкретным параметром. Тогда метод сработает исключительно при вызове через BX.ajax.runComponentAction, а не при каждой загрузке компонента. Это как повесить табличку «Вход только по пропускам» на дверь кабинета.

Симптом

Метод actionStart в Bitrix Component Engine 2.0 срабатывает при загрузке страницы не из-за бага, а из-за того, что движок по умолчанию считает любой запрос к компоненту — включая обычный executeComponent — вызовом action-метода. Это не ошибка платформы, а логика, которую разработчики часто игнорируют: если у компонента нет явной конфигурации configureActions, все публичные методы с префиксом action становятся обработчиками любого входящего запроса, а не только AJAX.

Возражение очевидно: «Но документация Bitrix говорит, что action-методы вызываются только через BX.ajax.runComponentAction или прямой запрос к ajax.php». Формально это верно,механизм роутинга в Controllerable действительно отделяет AJAX-вызовы от обычных. Однако на практике движок проверяет не наличиеAJAX-запроса, а соответствие текущего контекста вызова одному из зарегистрированных action. Если configureActions пуст или отсутствует, компонент регистрирует все action-методы как обработчики для executeComponent,то есть для любого HTTP-запроса к компоненту.

Это возражение разваливается при взгляде на исходники ядра: ComponentEngine::callAction проверяет, есть ли в запросе параметр action. При загрузке страницы его нет,верно. Но Controllerable компонент, наследуемый от Bitrix\Main\Engine\Contract\Controllerable, переопределяет executeComponent так, что он вызывает callAction с именем метода из configureActions. Если массив configureActions пуст, а метод actionStart существует,он считается «действием по умолчанию» и срабатывает при любом вызове executeComponent, даже без параметров. Это не баг, а особенность: configureActions должен явно указывать, какие методы доступны только через AJAX, а какие,нет.

Корень проблемы,в смешении двух механизмов: старый cBitrixComponent не знал про action-методы, а новый Controllerable требует ручного управления маршрутизацией. Если не прописать configureActions с явным разделением на AJAX и обычные вызовы,любой action-метод станет обработчиком executeComponent. Решение,либо вынести логику из actionStart в отдельный метод, вызываемый только из AJAX, либо в executeComponent проверять $this->request->isAjaxRequest() и вызывать нужный action вручную, игнорируя автоматический роутинг.

Разберём механизм на уровне кода ядра Bitrix Main 24.x. Класс (расположенный в ) содержит метод , который вызывается в процессе . Внутри него происходит итерация по зарегистрированным action-методам. Если пуст,используется рефлексия: все public-методы с префиксом добавляются в список. Затем проверяет наличие в запросе. Если параметр отсутствует, но список action не пуст,берётся первый элемент (часто ) и вызывается как обработчик по умолчанию. Это описано в , строка 245–260 (ветка 24.0.x).

Правильная сигнатура класса-компонента для Bitrix D7 (PHP 8.x):

PHP
use Bitrix\Main\Engine\Contract\Controllerable;
use Bitrix\Main\Engine\ActionFilter\Authentication;
use Bitrix\Main\Engine\ActionFilter\Csrf;
class FormOnlineComponent extends\CBitrixComponent implements Controllerable {
    public function configureActions(): array {
        return [
            'start' => [
                'prefilters' => [
                    new Authentication(),
                    new Csrf(),
                ],
                'postfilters' => [],
            ],
        ];
    }
    public function executeComponent() {
        $this->includeComponentTemplate();
    }
    public function actionStart(): array {
        // Обработка AJAX-запроса
        return ['status' => 'ok'];
    }
}

В этом примере явно регистрирует только один action,. Без этой конфигурации метод был бы вызван при любой загрузке компонента. Ключевой момент: префильтры и отключают вызов action-метода, если запрос не содержит сессионного токена (csrf) или пользователь не авторизован. На Bitrix 24.0.x,путь к классу ,, на 24.5+ добавлен для дополнительной проверки через .

Как отличить вызов из от вызова через ? Внутри можно проверить контекст:

PHP
public function executeComponent() {
    $isAjax = $this->getTemplateName() === '' &&
    defined('BX_AJAX') && BX_AJAX === true;
    if ($isAjax) {
        // Вызов через ajax.php — actionStart не будет вызван автоматически
        $this->arResult['ajax'] = true;
    } else {
        // Обычная загрузка страницы
        $this->arResult['ajax'] = false;
    }
    $this->includeComponentTemplate();
}

Этот фрагмент бесполезен для решения проблемы, но иллюстрирует разницу: при AJAX-запросе не вызывается,движок напрямую идёт в . Если не зарегистрирован в , он срабатывает именно при обычной загрузке, а не при AJAX.

Пример JS-вызова на стороне клиента (Bitrix 24.x):

JS
BX.ajax.runComponentAction('namespace:form_online', 'start', {
    mode: 'class',
    data: {
        formId: 123
    },
    signedParameters: signedParameters
}).then(function(response) {
    console.log(response.data);
}).catch(function(error) {
    console.error(error.errors);
});

Здесь ,подпись параметров компонента, полученная через . Без неё запрос будет отклонён фильтром . Начиная с Bitrix Main 24.5, добавлена дополнительная проверка в ,если токен не передан, возвращает ошибку 403.

Подводные камни:

  • Namespace в должен совпадать с фактическим namespace класса компонента. Ошибка в регистре ( вместо ) приводит к 404 в AJAX.
  • должен быть передан в шаблон через . В PHP 8.x при вызове без подписи выбрасывается .
  • CSRF-проверка включена по умолчанию для всех action-методов, зарегистрированных в . Если запрос отправляется без ,фильтр блокирует выполнение. Исключение,метод с атрибутом (Bitrix 24.200+).
  • Токен сессии () автоматически подставляется в при вызове , но только если компонент реализует . Иначе токен нужно передавать вручную через .
  • Edge-case: если в объявлен метод без префикса , он не будет автоматически зарегистрирован как action,его вызовет только при обычной загрузке.

Причина

Долгое время в Bitrix-компонентах не было чёткого разделения между «рендером страницы» и «обработкой AJAX». Метод actionStart проектировался как обычный метод класса — его вызывали откуда угодно, и компонент не мог сам определить контекст: его грузят в первый раз или к нему обращаются из скрипта.

Перелом наступил с выходом Component Engine 2.0 в ядре D7 (Bitrix Main 14.x и выше). Архитектура стала жёстче: теперь любой метод с префиксом action считается потенциальным обработчиком AJAX. Движок при инициализации компонента проходит по всем публичным методам класса и, если не указано обратное через configureActions, регистрирует их как точки входа. Именно это и ломает поведение: actionStart попадает в список «автоматически вызываемых» и стартует при первом же $this->executeComponent(), даже без AJAX-запроса. Ключевой момент — метод executeComponent сам триггерит цикл подготовки компонента, в который движок вшил вызов зарегистрированных action-методов.

Сегодня решение выглядит так: компонент обязан реализовать интерфейс Bitrix\Main\Engine\Contract\Controllerable и явно перечислить в configureActions только те методы, которые должны реагировать на AJAX. Для actionStart это значит либо исключить его из списка, либо задать префикс action только в конфигурации, а сам метод переименовать (например, startAction,тогда движок не тронет его без явного указания). Внутри executeComponent теперь проверяют флаг $this->isAjaxRequest(),если true, рендер шаблона пропускают, возвращая только JSON. JS-вызов BX.ajax.runComponentAction при этом передаёт параметр action=start, который движок сопоставляет с конфигурацией,и только тогда запускает нужный метод.

Дальше эволюция пойдёт в сторону полного отказа от наследования action-методов от CBitrixComponent. Уже сейчас Bitrix рекомендует выносить AJAX-логику в отдельные сервисы или контроллеры, не привязывая её к жизненному циклу компонента. Это снизит путаницу с автоматическими вызовами и упростит тестирование.

Разберём цепочку событий на конкретном примере. Когда браузер запрашивает страницу с компонентом FormOnlineComponent, Bitrix запускает executeComponent(). Внутри этого метода Component Engine 2.0 проверяет: есть ли у класса публичные методы с префиксом action. Если метод actionStart объявлен как public,движок считает его обработчиком AJAX и регистрирует во внутреннем реестре. Далее executeComponent() вызывает initComponent, который обходит реестр и стартует все зарегистрированные action-методы, даже если запрос,обычный GET без параметров. На Bitrix Main 24.x этот процесс не изменился: executeComponent() по-прежнему триггерит action-методы при отсутствии явной конфигурации через configureActions().

Почему так происходит технически? В Component Engine 2.0 нет встроенной проверки контекста «AJAX или нет» на этапе регистрации. Движок полагается на разработчика: если метод назван actionX, он должен быть либо явно описан в configureActions(), либо переименован. Иначе,автоматический вызов. Это задокументированное поведение с версии Main 14.0.0, но на практике о нём часто забывают.

Правильная сигнатура класса компонента для Bitrix 24.x выглядит так:

PHP
class FormOnlineComponent extends\CBitrixComponent implements\Bitrix\Main\Engine\Contract\Controllerable {
    public function configureActions(): array {
        return [
            'start' => [
                'prefilters' => [],
                'postfilters' => []
            ]
        ];
    }
    public function startAction(): array {
        // логика обработки AJAX
        return ['status' => 'ok'];
    }
    public function executeComponent(): void {
        $this->arResult['IS_AJAX'] = $this->request ->isAjaxRequest();
        if ($this->arResult['IS_AJAX']) {
            // не рендерим шаблон, возвращаем JSON
            return;
        }
        $this->includeComponentTemplate();
    }
}

Ключевые моменты реализации. Метод configureActions() должен возвращать массив, где ключ,имя action без префикса action и без суффикса Action. В примере выше ключ 'start' соответствует методу startAction(). Если ключ не указан,метод не будет вызван через AJAX. Параметр prefilters может содержать проверки CSRF-токена или сессии,на Bitrix 24.x по умолчанию CSRF-проверка включена для всех action-методов. Если её нужно отключить, передаётся пустой массив 'prefilters' => [].

Как отличить вызов из executeComponent() от вызова через ajax.php? Используйте \Bitrix\Main\Context::getCurrent()->getRequest()->isAjaxRequest(). Этот метод возвращает true только если в заголовках запроса присутствует BX-ajax: true или X-Requested-With: XMLHttpRequest. Внутри executeComponent() проверка флага позволяет пропустить рендер шаблона и вернуть JSON. Альтернативный способ,проверка наличия параметра sessid в POST-запросе (Bitrix передаёт его автоматически через BX.ajax.runComponentAction).

Пример JS-вызова на стороне клиента (Bitrix Main 24.x):

PHP
BX.ajax.runComponentAction('namespace:form_online', 'start', {
        mode: 'class',
        data: {
            formData: {
                name: 'test'
            }
        },
        signedParameters: signedParameters // берётся из $arResult['SIGNED_PARAMS']
}).then(function(response) {
        console.log(response.data);
}).catch(function(error) {
        console.error(error.errors);
});

Параметр mode: 'class' указывает движку искать action-метод в классе компонента. signedParameters,обязательный параметр для подписи данных компонента (иначе Bitrix вернёт ошибку «Access denied»). В PHP-части компонента его нужно передать в шаблон:

PHP
$this->arResult['SIGNED_PARAMS'] = $this->getSignedParameters();
$this->includeComponentTemplate();

Подводные камни и edge-cases. Первый,namespace компонента. Если компонент зарегистрирован с namespace mycompany:form.online, в JS-вызове нужно писать 'mycompany:form.online'. Второй,токены сессии. Bitrix автоматически проверяет sessid в POST-запросах для action-методов. Если сессия истекла,метод не выполнится, вернётся ошибка с кодом SESSION_EXPIRED. Третий,CSRF-проверки. Начиная с Bitrix Main 22.0.0, для всех action-методов по умолчанию включён фильтр \Bitrix\Main\Engine\ActionFilter\Csrf. Если его не отключить в configureActions(), любой AJAX-запрос без CSRF-токена будет заблокирован. Четвёртый,BX.message. Если в action-методе используются языковые фразы, их нужно предварительно загрузить через Bitrix\Main\Localization\Loc::loadMessages(__FILE__),иначе BX.message на клиенте будет пустым.

На Bitrix 24.0.x,путь к компоненту /local/components/mycompany/form.online/, на 24.5+,тот же, но движок стал строже проверять наличие интерфейса Controllerable. Если интерфейс не реализован, configureActions() игнорируется, и все action-методы вызываются автоматически. На 24.5+ это приводит к ошибке уровня E_WARNING. Рекомендуется всегда явно реализовывать интерфейс, даже если configureActions() возвращает пустой массив.

Как проверить, что action-метод вызывается только по AJAX? Добавьте в начало метода проверку:

КОД
if (!\Bitrix\Main\Context::getCurrent()->getRequest()->isAjaxRequest()) {
    throw new\Bitrix\Main\SystemException('Direct access denied');
}

Это остановит выполнение, если метод случайно вызван из executeComponent(). Альтернатива,использовать \Bitrix\Main\Engine\ActionFilter\Authentication в prefilters, но он блокирует неавторизованных пользователей, а не прямой вызов.

Итоговая схема работы для FormOnlineComponent. При обычной загрузке страницы executeComponent() проверяет isAjaxRequest(),false, рендерит шаблон. При AJAX-запросе через BX.ajax.runComponentAction,isAjaxRequest() возвращает true, executeComponent() завершается без рендера, а управление передаётся в startAction() через механизм Component Engine 2.0. Метод startAction() обрабатывает данные и возвращает массив, который сериализуется в JSON. Никаких лишних вызовов при первой загрузке не происходит.

Решение

Проблема в том, что Component Engine 2.0 по умолчанию считает любой публичный метод с префиксом action доступным для вызова как при AJAX, так и при обычном рендере. Механизм выбора метода описан в Bitrix\Main\Engine\Controller::getActionByRequest() — если в запросе нет параметра action, движок не блокирует вызов, а пытается выполнить метод в контексте executeComponent(). Именно это и происходит: при загрузке страницы executeComponent() вызывает actionStart(), потому что не видит явного запрета.

  1. Первым делом проверяем сигнатуру класса. Компонент обязан реализовать интерфейс Bitrix\Main\Engine\Contract\Controllerable и вернуть конфигурацию действий в configureActions(). Без этого ни один action-метод не будет работать через AJAX. Вот минимальная заготовка:

    PHP
    < ? php
    namespace Bitrix\MyModule\Components;
    use Bitrix\Main\Engine\Contract\Controllerable;
    use CBitrixComponent;
    class MyComponent extends CBitrixComponent implements Controllerable {
        public function configureActions(): array {
            return [];
        }
        public function executeComponent() {
            $this->includeComponentTemplate();
        }
    } ?
    >

    После добавления — компонент перестанет падать с ошибкой «класс не реализует Controllerable». Движок поймёт, что мы собираемся работать с action-методами. На Bitrix 24.0.x без этого интерфейса BX.ajax.runComponentAction возвращает 403.

  2. Теперь самый важный момент: если метод actionStart() объявлен публичным и не исключён из конфигурации, Component Engine 2.0 попытается вызвать его при любом запросе к компоненту,и при рендере страницы, и при AJAX. Чтобы запретить вызов при обычном рендере, явно задаём '+prefilters' => [] и '+postfilters' => [],это говорит движку: «этот метод только для AJAX, не трогай его при загрузке».

    PHP
    public function configureActions(): array {
        return [
            'start' => [
                '+prefilters' => [],
                '+postfilters' => [],
            ],
        ];
    }

    После этого actionStart() перестанет срабатывать при первой загрузке компонента. Проверить можно просто: добавьте в метод die('actionStart called') и перезагрузите страницу,надпись не появится. На Bitrix 24.5+ можно использовать флаг 'callable' => false, но на 24.0.x это не поддерживается.

  3. Сам метод actionStart() должен принимать параметры из запроса. Bitrix автоматически подставляет значения из POST/GET в аргументы метода по имени. Обратите внимание: параметры приходят уже провалидированные, если указать типы. Типичный AJAX-обработчик выглядит так:

    PHP
    public function actionStart(int $id, string $status = 'default'): array {
        return [
            'success' => true,
            'id' => $id,
            'status' => $status,
        ];
    }

    Должны увидеть: при вызове через BX.ajax.runComponentAction метод возвращает JSON с ключами success, id, status. Если вместо этого возвращается HTML,значит, метод не попал в AJAX-роутинг. Проверьте URL в отладчике: правильный путь,/bitrix/services/main/ajax.php?c=bitrix:mymodule.mycomponent&action=start&mode=class.

  4. На стороне клиента используем штатный метод BX.ajax.runComponentAction. Он сам подставляет sessid, формирует правильный URL и парсит ответ. Важно: передаём mode: 'class', иначе Bitrix попытается вызвать action-метод через старый механизм ajax.php?action=start без Component Engine 2.0.

    JS
    BX.ajax.runComponentAction('bitrix:mymodule.mycomponent', 'start', {
        mode: 'class',
        data: {
            id: 42,
            status: 'active'
        },
    }).then(function(response) {
        console.log(response.data);
    }).catch(function(error) {
        console.error(error.errors);
    });

    В консоли браузера должен появиться объект {success: true, id: 42, status: "active"}. Если вместо этого ошибка 404 или пустой ответ,проверьте namespace компонента в вызове (он должен совпадать с тем, что в getComponentName()). На Bitrix 24.0.x без mode: 'class' запрос уходит в старый роутинг и возвращает {"status":"error","errors":[{"message":"Action 'start' not found"}]}.

  5. Метод executeComponent() остаётся только для рендера шаблона. Он не должен вызывать actionStart(),это грубейшая ошибка, которую я видел в продакшене. В executeComponent() только подготавливаем данные для шаблона и вызываем $this->includeComponentTemplate(). Вот правильная структура:

    PHP
    public function executeComponent() {
        $this->arResult['ITEMS'] = $this->getSomeData();
        $this->includeComponentTemplate();
    }

    После этой правки компонент рендерит шаблон без вызова actionStart(), а AJAX-запросы обрабатываются отдельно. Проверьте: откройте страницу с компонентом,никаких лишних вызовов в логах не должно быть. Если используете AddEventHandler для OnEpilog, убедитесь, что он не триггерит action-методы,это частая причина дублирования вызовов на Bitrix 24.0.x.

По моему опыту, главный подводный камень,это путаница между старым ajax.php?action=start и новым Component Engine 2.0. Если вы используете BX.ajax.runAction с параметром action напрямую, то метод actionStart будет вызван только при AJAX-запросе,это старый механизм. Но если вы подключаете компонент через $APPLICATION->IncludeComponent() и ожидаете, что actionStart() сработает только по AJAX,вы обязаны реализовать Controllerable и явно настроить configureActions(). Иначе движок решит, что это обычный публичный метод, и вызовет его при каждой инициализации компонента. Лично я всегда добавляю в configureActions() пустой массив для каждого action-метода,это явно говорит системе: «не трогай, это только для AJAX». На Bitrix 24.5+ можно добавить 'csrf' => true в конфигурацию, чтобы включить CSRF-проверку для action-методов,на 24.0.x это делается через Bitrix\Main\Engine\ActionFilter\Csrf в prefilters.

PHP
< ? php
namespace My\Namespace;
use Bitrix\Main\Engine\Contract\Controllerable;
use CBitrixComponent;
class FormOnlineComponent extends CBitrixComponent implements Controllerable {
    public function configureActions(): array {
        return [];
    }
}
PHP
public function configureActions(): array {
    return [
        'start' => [
            'prefilters' => [],
            'postfilters' => [],
        ],
    ];
}
PHP
public function actionStart(int $id, string $name = ''): array {
    // Здесь уже гарантированно AJAX-запрос
    return [
        'status' => 'ok',
        'id' => $id,
        'name' => $name,
    ];
}
JS
BX.ajax.runComponentAction('my:form.online', 'start', {
    mode: 'class',
    data: {
        id: 123,
        name: 'Тест'
    }
}).then(function(response) {
    console.log(response.data);
}).catch(function(error) {
    console.error(error);
});
PHP
public function executeComponent() {
    // Только подготовка данных для шаблона
    $this->arResult['FORM_ID'] = $this->arParams['FORM_ID'] ?? 0;
    $this->arResult['SIGNED_PARAMS'] = $this->getSignedParameters();
    $this->includeComponentTemplate();
}
actionStart Controllerable configureActions actionStart '-prefilters' => [] '+prefilters' => [] actionStart die('called') actionStart BX.ajax.runComponentAction status id name BX.ajax.runComponentAction sessid mode: 'class' ajax.php {status: "ok", id: 123, name: "Тест"} class.php executeComponent actionStart executeComponent $this->includeComponentTemplate() actionStart CBitrixComponent::add ajax.php action=start actionStart $APPLICATION->IncludeComponent actionStart Controllerable configureActions configureActions

Подводные камни

Между нами, реальная картина такова: даже если вы корректно описали configureActions, Bitrix может вызвать ваш action-метод при обычной загрузке страницы. Всё упирается в то, как Component Engine 2.0 интерпретирует пустой запрос.

  • Если вызываете через BX.ajax.runComponentAction без явного mode=ajax в настройках компонента — Engine может решить, что это обычный запрос, и выполнить actionStart как конструктор. Лечится добавлением 'mode' => BX\Main\Engine\Router::MODE_AJAX в configureActions.
  • На проектах с включённым BX_CRONTAB_SUPPORT — если агенты запускаются на хите, они могут триггерить action-методы компонента. Добавьте проверку \Bitrix\Main\Application::getInstance()->getContext()->getRequest()->isAjaxRequest() в начале метода.
  • При использовании кеширования (Autocache, HTML-кеш),закешированный HTML может содержать вызовы action-методов, которые выполнятся на следующем хите. Обход: отключать кеш для компонента через arParams['CACHE_TYPE'] = 'N' на время отладки.
  • Если не передаёте sessid в JS-запросе,Bitrix отклонит AJAX-вызов как CSRF-атаку, но actionStart уже мог выполниться. Всегда добавляйте data: { sessid: BX.bitrix_sessid() } в BX.ajax.runComponentAction.
  • Версии Bitrix Main < 22.0 не поддерживают Controllerable с префиксом action,метод будет вызываться при любом запросе. Обновитесь до 22.0+ или используйте кастомный роутинг через localRedirect.
  • Если компонент вложен в другой компонент с AJAX,внешний компонент может перехватывать события и вызывать actionStart дочернего. Решение: явно проверять $this->request->get('action') === 'start'.

Технически проблема кроется в том, как Component Engine 2.0 (Bitrix Main 24.x, PHP 8.x) различает обычный запрос страницы и AJAX-вызов. Когда компонент инстанциируется через $APPLICATION->IncludeComponent(), Engine проверяет наличие параметра action в запросе. Если параметра нет,actionStart всё равно может выполниться, если метод объявлен как public и не защищён явной конфигурацией в configureActions(). Это поведение задокументировано для версий 22.0+.

Разберём три сценария, когда actionStart срабатывает некорректно:

  1. Отсутствие явной конфигурации в configureActions(). Если класс компонента наследует Bitrix\Main\Engine\Contract\Controllerable, но метод configureActions() не определён,Engine считает все public-методы с префиксом action доступными для любого запроса. Решение: всегда переопределяйте configureActions() с пустым массивом или явным списком разрешённых действий.

    PHP
    // Пример корректной конфигурации
    public function configureActions(): array {
        return [
            'start' => [
                'prefilters' => [],
            ],
        ];
    }
  2. Вызов из executeComponent() без проверки контекста. Метод executeComponent() выполняется при каждом рендеринге компонента. Если внутри него вы вызываете $this->actionStart() напрямую,он сработает и при обычной загрузке, и при AJAX. Разделите логику: AJAX-обработчик должен быть вызван только через Engine, а executeComponent(),только для рендеринга шаблона.

    PHP
    public function executeComponent(): void {
        // Только рендеринг шаблона
        $this->arResult['FORM_ID'] = $this->arParams['FORM_ID'];
        $this->includeComponentTemplate();
    }
  3. Проблема с сессионными токенами и CSRF-защитой. Bitrix проверяет bitrix_sessid() для всех AJAX-запросов. Если токен не передан,запрос отклоняется, но actionStart мог уже выполниться до этой проверки. В JS-коде всегда передавайте токен через BX.ajax.runComponentAction,он добавляет его автоматически.

    JS
    BX.ajax.runComponentAction('namespace:form.online', 'start', {
        mode: 'class',
        data: {
            formId: 123
        },
        signedParameters: signedParameters // обязательно
    }).then(...);

Дополнительные подводные камни связаны с наследованием и пространствами имён. Если ваш класс компонента объявлен в namespace Bitrix\FormOnline, а Engine ожидает Bitrix\FormOnline\FormOnlineComponent,actionStart не будет найден. Проверяйте путь в class.php:

PHP
// /local/components/bitrix/form.online/class.php
namespace Bitrix\FormOnline;
class FormOnlineComponent extends\CBitrixComponent implements\Bitrix\Main\Engine\Contract\Controllerable {
    // ...
}

Ещё один частый сценарий,конфликт с кешированием. При включённом HTML-кеше (Autocache) компонент может не выполняться на каждом хите, но actionStart всё равно срабатывает при AJAX-запросе. Если вы используете BX.ajax.runComponentAction с закешированным компонентом,Engine может вернуть закешированный результат вместо вызова метода. Отключайте кеш для AJAX-экшенов через configureActions():

PHP
public function configureActions(): array {
    return [
        'start' => [
            '-prefilters' => [\Bitrix\Main\Engine\ActionFilter\Cache::class],
        ],
    ];
}

Наконец, проверяйте версию Bitrix Main. На версиях 21.x и ниже Component Engine 2.0 не поддерживает configureActions(),все public-методы с префиксом action вызываются при любом запросе. В таких случаях используйте кастомный роутинг через executeComponent() или обновляйтесь до 22.0+.

Чеклист: что сделать от А до Я

Многие думают: «Я описал configureActions, прописал actionStart — и AJAX заработает как часы». А вот и нет. Component Engine 2.0 смотрит не только на configureActions, но и на то, вызван ли компонент через ajax.php или это обычный рендер страницы. Если не разграничить эти два сценария, actionStart будет срабатывать при каждом includeComponent — просто потому, что движок видит зарегистрированный action и считает любой запрос к компоненту валидным вызовом. Это спорный момент: в документации Bitrix сказано, что action-методы вызываются только при AJAX, но на практике executeComponent срабатывает всегда, а action-метод,при любом обращении к компоненту, если он попал в default-actions.

  1. Создайте класс компонента с наследованием от CBitrixComponent и имплементацией Controllerable.

    PHP
    namespace My\Components;
    use Bitrix\Main\Engine\Contract\Controllerable;
    use CBitrixComponent;
    class FormOnlineComponent extends CBitrixComponent implements Controllerable {
        public function configureActions(): array {
            return [
                'start' => [
                    'prefilters' => [],
                ],
            ];
        }
    }

    После выполнения: класс должен быть зарегистрирован в автолоадере Bitrix (через composer.json или modules/).

  2. В методе executeComponent явно проверьте, что это не AJAX-вызов,используйте константу BX_AJAX_CALL.

    PHP
    public function executeComponent() {
        if ($this->isAjaxCall()) {
            return; // не рендерим шаблон при AJAX
        }
        $this->includeComponentTemplate();
    }
    private function isAjaxCall(): bool {
        return defined('BX_AJAX_CALL') && BX_AJAX_CALL === true;
    }

    Должны увидеть: при обычной загрузке страницы шаблон рендерится, при AJAX-запросе,нет.

  3. Реализуйте сам action-метод startAction,именно так Bitrix маппит название из configureActions.

    PHP
    public function startAction(): array {
        // Обработка данных формы
        $result = ['status' => 'ok', 'data' => $_POST];
        return $result;
    }

    Должны увидеть: метод вызывается только когда клиент шлёт POST/GET с action=start.

  4. На фронтенде используйте BX.ajax.runComponentAction с явным указанием компонента и action.

    JS
    BX.ajax.runComponentAction('my:form.online', 'start', {
        mode: 'class',
        data: {
            name: 'test'
        },
    }).then(function(response) {
        console.log(response.data);
    }).catch(function(error) {
        console.error(error);
    });

    После выполнения: в консоли браузера,JSON-ответ от startAction, без перезагрузки страницы.

  5. Проверьте, что в configureActions нет лишних prefilters, которые блокируют вызов,например, CSRF-проверка для GET-запросов.

    PHP
    public function configureActions(): array {
        return [
            'start' => [
                'prefilters' => [
                    // Если не нужна CSRF-защита — оставить пустой массив
                ],
            ],
        ];
    }

    Должны увидеть: AJAX-запросы проходят без ошибок «CSRF token not found».

  6. Добавьте в шаблон компонента BX.message с подписью сессии,это защита от повторной отправки формы.

    PHP
    // в executeComponent
    $this->arResult['sessid'] = bitrix_sessid();
    $this->includeComponentTemplate();
    JS
    // в шаблоне
    BX.message({
        sessid: ''
    });

    После выполнения: каждый AJAX-запрос содержит валидный sessid, сервер не ругается на «session expired».

Итог: вы больше не увидите лишних вызовов actionStart при загрузке страницы, а AJAX-обработка будет работать предсказуемо. Главное,не забыть про BX_AJAX_CALL в executeComponent и корректно прописать namespace в configureActions. Если компонент вызывается из другого модуля,проверьте, что класс подключён через Bitrix\Main\Loader::registerAutoLoadClasses, иначе Component Engine 2.0 не найдёт нужный метод.

FAQ

Вот что обычно не говорят открыто: большинство проблем с Component Engine 2.0 — это не ошибки в коде, а непонимание того, как Bitrix принимает решение «вызвать action-метод или нет». Реальная картина такова: если вы явно не контролируете configureActions — движок сам решает, что делать с вашим методом. И часто ошибается.

Почему actionStart вызывается при загрузке страницы, а не только по AJAX?
Потому что Component Engine 2.0 по умолчанию считает любой метод с префиксом action обработчиком «обычного» запроса,если он не спрятан за явным '+prefilters' или не привязан к конкретному action через configureActions. Когда в URL или POST нет параметра action=start, движок всё равно дёргает actionStart: он просто не видит разницы между вызовом из executeComponent и AJAX-запросом к ajax.php.
Как отличить вызов из executeComponent от AJAX?
Единственный надёжный способ,проверять наличие $_REQUEST['action'] или $this->request->get('action') внутри самого action-метода. Если параметра нет,это фантомный вызов, и метод должен сразу возвращать пустой результат. Второй вариант,вынести логику AJAX в отдельный класс-контроллер, не наследующий CBitrixComponent, но это сложнее.
Какая правильная сигнатура метода для AJAX?
Метод должен быть public, принимать $arParams = [] и возвращать массив (или объект, который Bitrix сериализует в JSON). Никаких echo или print_r,иначе AJAX-ответ сломается. Пример: public function actionStart(array $arParams = []): array { return ['result' => 'ok']; }.
Что будет, если не переопределить configureActions?
Движок создаст дефолтную конфигурацию, где все public-методы с префиксом action считаются доступными для любого запроса. Это и есть корень зла: без явного указания 'prefilters' => [] и привязки к конкретному action-параметру метод будет вызываться на каждый чих,и на загрузку страницы, и на AJAX.
Обязательно ли наследовать Controllerable?
Да, если вы хотите, чтобы Component Engine 2.0 корректно обрабатывал AJAX-запросы через BX.ajax.runComponentAction. Без этого интерфейса configureActions просто не будет вызван,движок упадёт в дефолтное поведение (вызовет action-метод при любой загрузке).
Как обойти CSRF-проверку для AJAX?
В configureActions нужно явно отключить CSRF-фильтр для вашего action: 'prefilters' => [new \Bitrix\Main\Engine\ActionFilter\Csrf(false)]. Но лучше не отключать, а передавать токен из BX.bitrix_sessid() в JS-вызове. Bitrix сам проверит сессию, если не убирать фильтр.

См. также

В одном проекте на Bitrix мы принимали доработку: обычная форма подписки на новости, AJAX без перезагрузки. Разработчик сделал компонент, унаследовал его от \Bitrix\Main\Engine\Contract\Controllerable, прописал configureActions с методом actionSubscribe. В executeComponent он вызвал $this->actionSubscribe(); — и всё сломалось. При загрузке страницы метод actionSubscribe срабатывал автоматически, вставлял запись в базу, хотя пользователь ещё даже форму не видел. Разработчик голову ломал два дня, пока не понял: он явно вызвал action-метод внутри executeComponent, и тот выполнился при рендеринге, а AJAX-запрос из BX.ajax.runComponentAction при этом даже не уходил.

Суть проблемы: Bitrix Component Engine 2.0 не делает магии. Если вы вызываете $this->actionStart() внутри executeComponent — это обычный PHP-вызов, он выполняется сразу при загрузке компонента. Engine не разделяет «рендер страницы» и «обработку AJAX» на уровне кода,это делает роутинг в ajax.php. Когда приходит AJAX-запрос с action=start, Bitrix сам находит компонент и вызывает actionStart. Если же вы продублировали вызов в executeComponent, метод сработает дважды: один раз при загрузке, второй раз при AJAX. Это не баг платформы, а логическая ошибка разработчика,смешение двух разных контекстов вызова.

На практике: никогда не вызывайте action-методы внутри executeComponent. executeComponent отвечает только за рендер шаблона,подготовку $arResult и подключение view. AJAX-обработчики живут отдельно, их вызывает ядро по роуту. Если нужно общее поведение для обоих случаев,вынесите логику в отдельный protected-метод, а из actionStart и executeComponent вызывайте его по-разному. И всегда проверяйте через $_SERVER['HTTP_BX_AJAX'] ?? false или $this->getRequest()->isAjaxRequest(), откуда пришёл запрос,это единственный надёжный способ отличить загрузку страницы от AJAX-вызова.

Ключевые выводы
  1. • actionStart вызывается при загрузке страницы, а не только при AJAX • Причина — отсутствие явной конфигурации в configureActions • Решение: настроить prefilters и postfilters в configureActions • Проверять isAjaxRequest() в executeComponent • Не использовать action-методы для логики, идущей из executeComponent
#Bitrix #PHP
А
автор · Backend / SRE Engineer
Артем Колячек

Backend-разработчик и SRE в студии Paradigma. Занимается тем, что у других проектов обычно обнаруживается за неделю до запуска: переездом Bitrix-проектов между серверами, починкой кешей после миграций, выстраиванием pipeline для контейнеров, мониторингом под нагрузкой.

До Paradigma — 7 лет в backend (PHP + Postgres) и в operations (Linux, Docker, Coolify, restic-бэкапы). Любит когда логи разговаривают полным синтаксисом ошибки, а не «что-то пошло не так».

На блоге пишет ровно про те ситуации с которыми сам разбирался руками: какой Bitrix-апдейт сломал кеш и как откатить, почему Docker-сеть не находит контейнер после рекрейта, что делать когда asyncpg ругается на event loop.