1С Битрикс · 6 мин чтения

CBitrixComponent: жизненный цикл компонента и порядок методов

CBitrixComponent - абстрактный класс из /bitrix/modules/main/classes/general/component.php, от которого наследуются все компоненты в 1С-Битрикс: и системные из битрикс.каталог, и кастомные, которые пишет разработчик под конкретный проект. Когда беру в работу нетиповой компонент - форма заявки с интеграцией в CRM, блок с расчётом доставки через СДЭК или вывод каталога с нестандартной фильтрацией - первое, что проверяю в чужом коде: правильно ли выстроен порядок вызова методов. Ошибка в последовательности initComponent, onPrepareComponentParams и executeComponent на практике даёт три типовых симптома - параметры компонента приходят пустыми, кеш отдаёт чужие данные другому пользователю, шаблон получает не тот arResult. Разберу, что и в каком порядке выполняет движок, и покажу рабочий скелет компонента.

Из чего состоит CBitrixComponent и что от него наследуется

Класс объявлен в модуле main и реализует интерфейс IBitrixComponent. От него наследуется CBitrixComponentTemplate для шаблонов и, что важнее для разработчика, любой class.php компонента вида:

class MyCompanyDeliveryCalcComponent extends CBitrixComponent
{
    public function onPrepareComponentParams($arParams) { ... }
    public function executeComponent() { ... }
}

Внутри объекта компонента после инициализации доступны служебные свойства: $this->arParams - нормализованные входные параметры, $this->arResult - данные для шаблона, $this->__component_name, $this->__path, $this->__template - метаданные о самом компоненте и подключённом шаблоне. Их не нужно объявлять руками - движок раскладывает их на этапе инициализации, до вызова executeComponent. Именно эта инициализация и задаёт тот самый порядок методов, который часто путают.

Порядок вызова методов: жизненный цикл компонента от запроса до HTML

Когда компонент подключается через $APPLICATION->IncludeComponent() или CBitrixComponent::includeComponentClass(), движок вызывает методы строго в таком порядке:

Метод Когда вызывается Что делает
__construct() при создании объекта компонента переопределяю редко, только если нужна ранняя инициализация зависимостей
initComponent() сразу после создания объекта устанавливает пути компонента, регистрирует автозагрузку классов из /lib, подключает include.php при наличии
onIncludeComponentLang() после initComponent, до параметров подключает файл языковых сообщений lang/ru/component.php под текущий LANGUAGE_ID
onPrepareComponentParams($arParams) до executeComponent нормализует и валидирует arParams, задаёт значения по умолчанию
executeComponent() основной вызов единственный обязательный к переопределению метод, точка входа в бизнес-логику
includeComponentTemplate() вызывается изнутри executeComponent подключает template.php выбранного шаблона и component_epilog.php

Важный нюанс: onPrepareComponentParams вызывается системой отдельно от executeComponent, и если внутри него не вернуть массив $arParams, компонент получит null вместо параметров - частая причина, почему «параметры не доходят до шаблона», хотя на деле проблема на шаг раньше.

onPrepareComponentParams и initComponent: что происходит до executeComponent

initComponent переопределяю редко - там обычно только регистрация автозагрузки для собственных классов из папки /lib компонента:

public function initComponent()
{
    BitrixMainLoader::registerAutoLoadClasses('mycompany.delivery', [
        'MyCompany\Delivery\Calculator' => 'lib/calculator.php',
    ]);
}

A onPrepareComponentParams использую почти всегда - здесь провожу нормализацию входных данных, до которых ещё не добрался executeComponent:

public function onPrepareComponentParams($arParams)
{
    $arParams['CACHE_TIME'] = isset($arParams['CACHE_TIME']) ? (int)$arParams['CACHE_TIME'] : 3600;
    $arParams['DELIVERY_CITY_CODE'] = trim((string)($arParams['DELIVERY_CITY_CODE'] ?? ''));
    $arParams['USE_CACHE'] = ($arParams['USE_CACHE'] ?? 'Y') === 'Y';

    return $arParams;
}

Если пропустить приведение типов на этом шаге, каждая проверка вроде if ($arParams['CACHE_TIME'] > 0) внутри executeComponent превращается в источник багов - параметр может прийти строкой из .php-настроек компонента на странице, и сравнение отработает не так, как ожидается.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

executeComponent, кеширование и получение arResult

executeComponent - единственный метод, который обязателен. Внутри него обычно три шага: попытка достать данные из кеша, получение данных, если кеша нет, и подключение шаблона. Стандартная связка выглядит так:

public function executeComponent()
{
    if ($this->startResultCache($this->arParams['CACHE_TIME'], $this->arParams['DELIVERY_CITY_CODE'])) {
        try {
            $this->arResult = $this->getDeliveryData($this->arParams['DELIVERY_CITY_CODE']);
        } catch (Exception $e) {
            $this->abortResultCache();
            $this->arResult = ['ERROR' => $e->getMessage()];
        }
        $this->includeComponentTemplate();
    }
}

На практике здесь и живёт большинство ошибок кеширования. Если внутри блока кеша обращаться к $_SESSION, персональным скидкам или данным авторизованного пользователя, не вызвав abortResultCache(), первый посетитель зафиксирует свой персональный результат в кеше - и следующие пользователи увидят чужие данные. Я это ловил на компоненте расчёта доставки через СДЭК: калькулятор кешировал ответ API по городу, но забыл исключить из ключа кеша тип тарифа, и часть посетителей получала неверную стоимость до истечения TTL. Похожая история с интеграциями T‑Bank и WooCommerce-подобными витринами - если статус оплаты попадает в закешированный arResult, кеш начинает врать про статус заказа. Когда логика компонента завязана на внешний API с нестабильным откликом, разработку такого узла обычно закладываю отдельным этапом, и заказчикам, которым проще делегировать это целиком, предлагаю разработку кастомного компонента под задачу вместо доработки чужого нетипового кода.

includeComponentTemplate и порядок подключения шаблона

Метод includeComponentTemplate() выбирает шаблон по трём источникам - явно переданному имени, значению из SIGNED_PARAMETERS в GET-запросе (для AJAX-компонентов) и, если ничего не задано, шаблону .default. Дальше порядок такой: подключается header.php темы сайта (если компонент не в буфере), затем сам template.php с доступом к $arResult и $arParams в области видимости шаблона, затем component_epilog.php, если он есть в папке шаблона - там обычно добавляют CSS/JS через CJSCore или задают заголовок страницы через SetTitle. При компонентном AJAX-запросе (COMPONENT_TEMPLATE или AJAX_MODE=Y) footer/header сайта не подключаются вовсе, выводится только тело шаблона - это нужно держать в голове, если в template.php завязана логика на глобальные переменные шаблона сайта.

Каскад для составных и AJAX-компонентов

У композитных страниц и компонентов с CBitrixComponent::includeComponentTemplate в режиме AJAX порядок методов не меняется, меняется только то, что часть вывода буферизуется и возвращается через json. executeComponent вызывается так же, просто результат includeComponentTemplate() не пишется напрямую в буфер ответа, а перехватывается ядром для сборки JSON-ответа.

Частые ошибки при переопределении методов CBitrixComponent

За несколько лет работы с чужими и своими компонентами регулярно встречаю один и тот же набор багов:

  • Логика выборки данных из БД засунута в onPrepareComponentParams вместо executeComponent - метод вызывается системой раньше, чем ожидает разработчик, и падает на отсутствующих зависимостях модуля
  • Прямое обращение к $_REQUEST внутри executeComponent вместо $this->arParams - ломает переиспользование компонента на разных страницах с разными настройками
  • Отсутствие приведения типов CACHE_TIME и других числовых параметров - при передаче параметра строкой из визуального редактора кеш либо не работает вовсе, либо висит бесконечно
  • Персональные данные пользователя (скидки, статус заказа, регион) попадают в закешированный arResult без abortResultCache() - самая частая причина «чужих данных у другого пользователя»
  • Забытый .parameters.php или неверная структура папки .default/template.php - компонент не находит шаблон и падает с пустым выводом без внятной ошибки

Большинство этих ошибок ловятся на этапе код-ревью за пять минут, если знать, в каком порядке движок реально вызывает методы, а не полагаться на то, «как обычно бывает».

Чтобы сайт работал без сбоев

Техподдержка

от 15 000 ₽/мес

Подробнее →

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

В каком порядке вызываются методы компонента в 1С-Битрикс?

Сначала __construct(), затем initComponent(), потом onIncludeComponentLang() для подключения языковых файлов, следом onPrepareComponentParams() для нормализации входных параметров, и только после этого executeComponent() - единственный обязательный метод, внутри которого обычно вызывается includeComponentTemplate() для подключения шаблона.

Чем отличается onPrepareComponentParams от initComponent?

initComponent вызывается раньше и обычно используется для регистрации автозагрузки классов компонента и подключения include.php. onPrepareComponentParams вызывается позже, принимает на вход массив $arParams и должен вернуть его же после нормализации - приведения типов, значений по умолчанию, валидации. Бизнес-логику получения данных в оба метода выносить не стоит, для этого есть executeComponent.

Как кеширование влияет на порядок выполнения executeComponent?

StartResultCache() проверяет наличие валидного кеша по переданному ключу и времени жизни. Если кеш есть, метод возвращает false, и код внутри блока if вообще не выполняется - арResult и шаблон достаются из закешированного файла. Если кеша нет или он истёк, метод возвращает true, выполняется получение данных, и в конце вызывается includeComponentTemplate(), результат которого автоматически попадает в кеш при штатном завершении без abortResultCache().

Нужно ли переопределять конструктор CBitrixComponent?

В подавляющем большинстве компонентов - нет, initComponent() полностью закрывает задачи ранней инициализации, включая регистрацию автозагрузки классов. Переопределять __construct() имеет смысл только при внедрении сторонних зависимостей до вызова родительских методов инициализации, и в этом случае обязательно нужно вызвать parent::__construct() первой строкой, иначе сломается вся дальнейшая цепочка initComponent → onPrepareComponentParams → executeComponent.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно