Свой компонент Битрикс: чек-лист перед продакшеном
Компонент, который на тестовом стенде открывается мгновенно, в проде упирается в базу и теряет заголовок страницы. Разбираем, что проверить в class.php, кеше, выборке и шаблоне до релиза.

Самописный компонент почти всегда проходит приёмку: на деве каталог открывается быстро, вывод корректный, параметры в редакторе выставляются. Потом он попадает в прод, и выясняется, что на каждый хит он делает отдельный запрос к базе на каждый элемент списка, кеша у него нет вообще, а SetTitle() вызывается внутри кеша и после первой генерации заголовок страницы залипает на одной категории. Ниже — чек-лист по пунктам, которые ломаются чаще всего. Ориентир: Битрикс 23.x–24.x, ядро D7, PHP 8.1+, компонент с class.php.
Размещение и структура
Первое, что проверяется до чтения кода, — путь. Компонент должен лежать в /local/components/вендор/имя.компонента/, а не в /bitrix/components/. Папка /bitrix/ перетирается обновлениями ядра, и ваш компонент однажды просто исчезнет вместе с правками. Вендор — не bitrix и не пустая строка: пространство имён bitrix занято и при совпадении имени будет найден чужой компонент.
Минимальный набор файлов и кто за что отвечает:
| Файл | Зачем нужен | Что проверить перед релизом |
|---|---|---|
class.php | Логика компонента | Есть onPrepareComponentParams(), подключение модулей проверяется на результат |
component.php | Точка входа (для процедурных компонентов) | Если есть class.php, здесь не должно быть дублирующей логики |
.parameters.php | Параметры в визуальном редакторе | Описан CACHE_TIME, нет параметров, которых не читает код |
.description.php | Название и раздел в списке компонентов | Нужен только если компонент ставится через редактор |
lang/ru/ | Языковые файлы | Нет строк, захардкоженных в class.php и шаблоне |
templates/.default/ | Шаблон по умолчанию | Шаблон не содержит запросов к базе |
Параметры: валидация до кеша, а не после
Типовая ошибка — приводить типы и подставлять значения по умолчанию в executeComponent(). К этому моменту параметры уже участвовали в формировании ключа кеша, и IBLOCK_ID = "5" и IBLOCK_ID = 5 дадут две разные записи кеша с одинаковым содержимым. Всё приведение типов живёт в onPrepareComponentParams().
<?php
// /local/components/mycompany/catalog.section/class.php
// Битрикс 23.x-24.x, ядро D7
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
Loc::loadMessages(__FILE__);
class CatalogSectionComponent extends CBitrixComponent
{
public function onPrepareComponentParams($arParams)
{
$arParams['IBLOCK_ID'] = (int)($arParams['IBLOCK_ID'] ?? 0);
$arParams['SECTION_CODE'] = trim((string)($arParams['SECTION_CODE'] ?? ''));
$arParams['ELEMENT_COUNT'] = (int)($arParams['ELEMENT_COUNT'] ?? 20);
// верхняя граница обязательна: иначе ELEMENT_COUNT=100000
// из GET-параметра положит базу
if ($arParams['ELEMENT_COUNT'] <= 0 || $arParams['ELEMENT_COUNT'] > 100) {
$arParams['ELEMENT_COUNT'] = 20;
}
$arParams['CACHE_TIME'] = isset($arParams['CACHE_TIME'])
? (int)$arParams['CACHE_TIME']
: 3600;
return $arParams;
}
public function executeComponent()
{
if (!Loader::includeModule('iblock')) {
ShowError(Loc::getMessage('MYCOMP_NO_IBLOCK_MODULE'));
return;
}
if ($this->arParams['IBLOCK_ID'] <= 0) {
ShowError(Loc::getMessage('MYCOMP_BAD_IBLOCK_ID'));
return;
}
$this->includeComponentTemplate();
}
}
Loader::includeModule() возвращает false, если модуль не установлен или не лицензирован. Результат проверяется всегда: на боевом сервере редакции отличаются от вашего стенда, и компонент, который падает с фатальной ошибкой на вызове CIBlockElement, роняет всю страницу.
Кеш: три уровня и цена каждого
Компонент без кеша — это не компонент, а SQL-запрос, обёрнутый в HTML. На одном посетителе разницы не видно, на тысяче видно только её.
| Подход | Когда данные обновляются | Чем платите |
|---|---|---|
| Без кеша | Мгновенно | Полная выборка на каждый хит, рост нагрузки линейно по трафику |
startResultCache() по времени | По истечении CACHE_TIME | Устаревшие данные до конца интервала: цена и наличие товара отстают |
startResultCache() + тегированный кеш* | При изменении элемента инфоблока | Нужен включённый управляемый кеш, кеш сбрасывается широко — по всему инфоблоку |
| Композитный кеш страницы | По правилам композита | Персональные блоки приходится выносить в динамические области |
Рабочий минимум для списочного компонента — второй или третий вариант:
use Bitrix\Main\Application;
$cacheKey = [
$this->arParams['IBLOCK_ID'],
$this->arParams['SECTION_CODE'],
$this->arParams['ELEMENT_COUNT'],
// права влияют на выборку - группы пользователя в ключ
$GLOBALS['USER']->GetUserGroupArray(),
];
if ($this->startResultCache($this->arParams['CACHE_TIME'], $cacheKey)) {
$this->arResult['ITEMS'] = $this->loadItems();
if (empty($this->arResult['ITEMS'])) {
// пустой результат не держим в кеше час
$this->abortResultCache();
ShowError(Loc::getMessage('MYCOMP_EMPTY_RESULT'));
return;
}
if (defined('BX_COMP_MANAGED_CACHE')) {
Application::getInstance()
->getTaggedCache()
->registerTag('iblock_id_' . $this->arParams['IBLOCK_ID']);
}
// ключи arResult, которые останутся доступны после кеша
$this->setResultCacheKeys(['SECTION_NAME']);
$this->includeComponentTemplate();
}
Три проверки по этому фрагменту. Первая: в ключ кеша входит всё, от чего зависит результат, включая группы пользователя и значимые GET-параметры, — иначе авторизованный получит страницу анонима. Вторая: ошибочный или пустой результат не кешируется, для этого есть abortResultCache(). Третья: includeComponentTemplate() вызывается внутри блока if, иначе шаблон отрендерится дважды.
Что нельзя оставлять внутри кеша
$APPLICATION->SetTitle()иAddChainItem()— выполнятся один раз при генерации и залипнут. Выносите вcomponent_epilog.phpшаблона, передавая значения через ключи, перечисленные вsetResultCacheKeys().- Счётчики просмотров, запись в журнал, отправку писем — любые побочные эффекты. При попадании в кеш они просто перестанут выполняться.
- Проверку прав на конкретный объект, если её результат не попал в ключ кеша.
- Вывод CSRF-токена: закешированный
bitrix_sessid()перестанет проходить проверку после смены сессии.
Выборка данных: где рождается N+1
Главный источник медленных компонентов — GetNextElement() с GetProperties() в цикле. Каждый вызов — отдельный запрос за свойствами элемента. Двадцать товаров на странице превращаются в двадцать запросов сверх основного. Свойства перечисляются в $select через префикс PROPERTY_:
$result = CIBlockElement::GetList(
['SORT' => 'ASC', 'ID' => 'DESC'],
[
'IBLOCK_ID' => $this->arParams['IBLOCK_ID'],
'ACTIVE' => 'Y',
'SECTION_CODE' => $this->arParams['SECTION_CODE'],
'INCLUDE_SUBSECTIONS' => 'Y',
],
false,
['nTopCount' => $this->arParams['ELEMENT_COUNT']],
[
'ID', 'NAME', 'CODE', 'DETAIL_PAGE_URL',
'PREVIEW_PICTURE', 'PREVIEW_TEXT',
'PROPERTY_ARTNUMBER',
]
);
$items = [];
while ($row = $result->Fetch()) {
$items[$row['ID']] = $row;
}
Два нюанса, которые стоит знать до релиза. DETAIL_PAGE_URL не придёт, если его нет в $select, — шаблон получит пустые ссылки. И выборка двух многозначных свойств в одном GetList размножает строки по декартову произведению значений: список из двадцати товаров внезапно отдаёт шестьдесят записей. Многозначные свойства лучше добирать одним отдельным запросом по собранным ID.
Про Fetch() и GetNext(): второй дополнительно прогоняет значения через экранирование. Это удобно, но делает данные непригодными для дальнейшей обработки, а в шаблоне создаёт иллюзию, что экранирование уже сделано. Выбирайте одно поведение осознанно и экранируйте в шаблоне сами.
Безопасность: четыре места, где течёт
- Вывод в шаблоне. Любое значение из базы или запроса — через
htmlspecialcharsbx(). Исключение только для полей с заведомо HTML-содержимым (детальное описание в режиме HTML). - Входные данные. Вместо
$_GETи$_POST—Bitrix\Main\Context::getCurrent()->getRequest(). Это не магическая защита, но единая точка, где видно, что компонент читает извне. - Формы и AJAX. На приём POST —
check_bitrix_sessid(). Если компонент обрабатывает AJAX черезControllerableиconfigureActions(), проверьте, какие префильтры вы отключили: отключив CSRF-фильтр «чтобы заработало», вы открыли действие всему интернету. Точные имена классов фильтров сверяйте в документации по вашей версии ядра. - Прямые SQL-запросы. Если без них не обойтись, строки прогоняйте через
getSqlHelper()->forSql(), а числа приводите кint. Конкатенация пользовательского ввода в запрос — брак, который проверяется поиском по коду перед релизом.
Что ломается в проде чаще всего
- Кеш не сбрасывается. Тегированный кеш работает только при включённом управляемом кеше в настройках главного модуля. На стенде он включён, на проде — не всегда.
- Файлы шаблона правили в
.default. При обновлении компонента или переносе правки теряются. Кастомизация живёт в копии шаблона в теме сайта. - Тяжёлая логика в
template.php. Запросы из шаблона выполняются даже при попадании в кеш результата не всегда так, как ожидается, и ломают всю модель кеширования. Данные готовитclass.php, шаблон только выводит. - Нет языковых файлов. Хардкод русских строк вылезет при включении второго языка административной части.
- Ошибки через
echo. Вывод текста ошибки посреди вёрстки ломает разметку и попадает в кеш. ИспользуйтеShowError()или собственную коллекцию ошибок вarResult.
* Тегированный кеш (управляемый кеш) — механизм, при котором запись кеша помечается тегом вида iblock_id_5. Когда элемент инфоблока меняется, модуль сбрасывает все записи с этим тегом, не дожидаясь истечения времени жизни. Имена тегов задаёт модуль, который данные отдаёт, поэтому для своих сущностей теги придумываете и сбрасываете вы сами.
Что делать после релиза
Снимите базовые показатели до того, как компонент попадёт на трафик: число запросов к базе и время генерации на один хит. В Битриксе для этого есть модуль «Монитор производительности» — на тестовом стенде он покажет и количество запросов, и самые долгие из них. Нормальный списочный компонент после прогрева кеша не должен делать запросов к базе вовсе.
Дальше смотрите на три вещи: долю попаданий в кеш, объём папки /bitrix/cache/ (разрастание — признак слишком мелкого ключа) и медленные запросы в логе СУБД после сброса кеша. Инвалидация кеша по собственным сущностям и перевод компонента на композит — отдельные темы, здесь мы закрыли только то, что проверяется перед первым выходом в прод.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: 1C-Bitrix: документация по API ядра D7, 1C-Bitrix: справочник API (классическое ядро)
Частые вопросы
У меня Битрикс старее, чем 23.x. Этот чек-лист подойдёт?
Компонент закеширован, но данные всё равно устаревают. Что смотреть первым?
Заголовок страницы залипает на одном значении. Как починить без отказа от кеша?
Почему список элементов отдаёт больше строк, чем элементов в разделе?
Можно оставить компонент в /bitrix/components, если проект не обновляется?
Нужно ли в компоненте писать свой ORM-слой вместо CIBlockElement?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.