Что такое компонент Битрикс и из чего он состоит
Компонент — это логическая единица Bitrix Framework, которая отделяет бизнес-логику от представления. В наших проектах это работает так: одна и та же карточка товара выводится на сайте в каталоге и одновременно попадает в мобильную выдачу через API — логика одна (запрос к инфоблоку, выборка свойств, торговых предложений, цен), а шаблоны разные. Мы описываем компонент один раз и подключаем его с разными шаблонами там, где это нужно.
Без компонентов любая правка вёрстки или логики вывода превращается в поиск по десяткам страниц. С компонентом всё изолировано: логика лежит в component.php, представление — в template.php, а связывает их массив $arResult. Плюс из коробки приходит кеширование: разработчик оборачивает тяжёлые выборки в CBitrixComponent::StartResultCache() и EndResultCache(), и страница не бьёт по базе при каждом заходе. В проектах с большим каталогом это разница между 200 мс и 4 секундами отклика.
Технически для каждого подключения создаётся свой экземпляр класса CBitrixComponent — оболочки компонента, которая живёт до конца подключения. Параметры, кеш и результат одного вызова не пересекаются с другим вызовом того же компонента на той же странице.
Как устроено полное имя компонента
Полное имя компонента состоит из двух частей через двоеточие: пространство_имен:имя_компонента. Пространство имён отделяет ваш код от системного и от кода других разработчиков. Стандартные компоненты живут в пространстве bitrix — отсюда bitrix:news.list, bitrix:catalog, bitrix:sale.order.ajax.
Свой компонент мы называем по имени студии или проекта: например, my:user.card — компонент «Карточка пользователя». Двоеточие здесь не декоративное: по нему CBitrixComponent понимает, в какой папке искать файлы. Имя после двоеточия тоже может содержать точку — это удобно для группировки: my:user.card, my:user.list, my:user.profile лежат в одной папке user.
Правило простое: никогда не называйте свои компоненты в пространстве bitrix — даже если очень хочется «прикинуться системным». При следующем обновлении платформы ваш код затрётся без предупреждения.
Где физически лежат компоненты
По умолчанию системные компоненты размещаются в /bitrix/components/bitrix/. Это ядро, которое обновляется через маркетплейс и систему обновлений, — трогать его нельзя. Рядом может лежать /bitrix/components/<партнёр>/ — компоненты сторонних разработчиков, установленные из Marketplace.
Ваш собственный код по правилам Bitrix Framework должен жить отдельно от ядра. Для этого есть папка /local/components/. Внутри неё вы создаёте подпапку с именем своего пространства имён — например, /local/components/my/ — и уже туда кладёте компоненты. Фреймворк при поиске компонента сначала смотрит в /local/, потом в /bitrix/. Значит, вы можете переопределить поведение системного компонента, не трогая ядро.
В проектах, где мы работаем с большим количеством кастомных компонентов, /local/components/ — единственное правильное место. Если компонент пишется под конкретный проект, он уходит в /local/; если он планируется как переиспользуемый между проектами — оформляется в модуль, но это уже отдельная история.
Какие файлы внутри папки компонента за что отвечают
Папка компонента — это не один файл, а набор файлов и подпапок, каждая со своей ролью. Разберём по порядку, что вы там увидите и зачем оно нужно.
component.php— основная логика: запросы к инфоблокам, подготовка$arResult, внутри доступны$arParams,$componentName,$componentTemplate..description.php— массив$arComponentDescriptionс названием, описанием и расположением компонента в визуальном редакторе..parameters.php— описание входных параметров, которые видит контент-менеджер при подключении компонента./lang/— языковые файлы; сюда же кладут подсказки к параметрам: ключи видаИМЯ_ПАРАМЕТРА_TIPв массиве$MESS./templates/— папки шаблонов; внутри каждой —template.phpи, при необходимости,style.css,script.js,result_modifier.php,component_epilog.php.result_modifier.php— подключается сразу послеcomponent.php; здесь дорабатывают$arResultперед передачей в шаблон.component_epilog.php— выполняется после отработки шаблона, доступен с версии 9.0; инструмент для действий при включённом кешировании./images/— картинки, иконки и прочие ресурсы компонента, которые не относятся к конкретному шаблону.
Не все файлы обязательны. Но component.php есть практически всегда — это «сердце» компонента, где живёт вся бизнес-логика. Остальные подключаются по необходимости.
Где лежат компоненты и как подключить свой через IncludeComponent
Типичный сценарий: в проекте есть готовая вёрстка раздела, инфоблок с данными, всё на месте — но нужно вывести список в нестандартном месте страницы, которого нет в шаблоне сайта. Или компонент подключается из section.php подраздела, а не через визуальный редактор. Или родительский комплексный компонент должен отрисовать внутри себя дочерний. Во всех этих случаях ручной вызов через CMain::IncludeComponent — единственный рабочий путь.
Метод существует с версии 5.1.7 и делает ровно то, что написано в названии: подключает указанный компонент с указанным шаблоном в том месте, где вызывается. Он принимает имя компонента в формате пространство_имён:имя_компонента (например, bitrix:news.list или my:user.card), имя шаблона строкой, массив параметров и — опционально — ссылку на родительский компонент.
По умолчанию компоненты лежат в /bitrix/components/; своё пространство имён обычно размещают в /local/components/, чтобы не конфликтовать с обновлениями ядра. В отличие от вызова через визуальный редактор, здесь вы полностью контролируете, какие параметры уйдут в $arParams, и можете подставить их из переменных ЧПУ или из результата другого компонента.
Сигнатура CMain::IncludeComponent и что означают аргументы
Полный вызов выглядит так: $APPLICATION->IncludeComponent($componentName, $componentTemplate, $arParams, $parentComponent, $arFunctionParams). Разберём по порядку, потому что именно здесь чаще всего путаются.
- $componentName — полное имя компонента с пространством имён, строка. Например,
'bitrix:news.list'. - $componentTemplate — имя шаблона компонента строкой:
'.default','flat'или ваш кастомный. Можно передать'', тогда подставится дефолтный. - $arParams — массив входных параметров. Ключи — идентификаторы параметров из
.parameters.php; внутри компонента они доступны как$arParams. - $parentComponent — ссылка на родительский компонент (объект
CBitrixComponentилиnull). Нужна, когда вы вкладываете один компонент в другой. - $arFunctionParams — служебный массив (кеш, возврат результата вместо вывода). В обычных вызовах оставляют пустым.
Минимальный вызов news.list в section.php
$APPLICATION->IncludeComponent(
"bitrix:news.list",
".default",
[
"IBLOCK_ID" => 12, // замените на ID вашего инфоблока
"PARENT_SECTION" => $arResult["VARIABLES"]["SECTION_ID"],
"CACHE_TYPE" => "A",
"CACHE_TIME" => 3600,
"NEWS_COUNT" => 20,
"SORT_BY1" => "ACTIVE_FROM",
"SORT_ORDER1" => "DESC",
],
false
);Как передать родительский компонент и зачем это нужно
Когда дочерний компонент вызывается из component.php родителя, четвёртым аргументом передают $this — так дочерний знает, кто его вызвал. Это даёт две вещи: наследование настроек кеша и корректную работу $component в шаблоне дочернего (через него доступны $component->getParent() и путь к шаблону родителя).
Без передачи родителя вложенный компонент отрисуется, но при AJAX-запросах и в режиме кеширования родителя поведение будет непредсказуемым — это мы разбираем ниже, в секции про AJAX.
// внутри component.php родительского компонента
$APPLICATION->IncludeComponent(
"bitrix:news.list",
"slider",
[
"IBLOCK_ID" => $arParams["IBLOCK_ID"],
"NEWS_COUNT" => $arParams["SLIDER_COUNT"],
"CACHE_TYPE" => $arParams["CACHE_TYPE"],
],
$this // ← родительский компонент
);Шаблон компонента Битрикс: как кастомизировать без правки ядра
Самый частый запрос на эту тему звучит так: «взяли компонент, поправили шаблон прямо в /bitrix/components/bitrix/news.list/templates/.default, всё заработало — а после обновления платформы правки исчезли». Это классика. Всё, что лежит в /bitrix/, — территория вендора. Файловая структура Bitrix Framework специально организована так, что программные компоненты ядра отделены от пользовательских файлов и файлов, определяющих внешнее представление сайта. То есть архитектура сама подталкивает к правильному пути: логику и шаблоны, которые вы меняете, нужно держать вне /bitrix/.
Когда мы только разбирались с этой механикой, приходилось переучиваться с привычки «правь где видишь». Правильный подход — скопировать нужный шаблон в шаблон сайта и работать уже там. Что это даёт по факту:
- обновления платформы не затирают ваши правки — они физически лежат в другой папке;
- шаблон подхватывается автоматически, менять вызов компонента не нужно;
- можно держать несколько вариантов вывода одного компонента под разные разделы;
- git видит только ваши файлы, а не диффы по ядру при каждом обновлении.
Дальше разберём, куда именно копировать и что менять внутри.
Куда копировать шаблон: /local/templates/<шаблон_сайта>/components/<пространство>/<имя>/<шаблон>/
Полное имя компонента включает пространство имён: пространство_имен:имя_компонента — например, bitrix:news.list. По умолчанию компоненты лежат в /bitrix/components/bitrix, а вот пользовательская кастомизация шаблонов живёт в шаблоне сайта. Структура пути повторяет исходную, но с корнем в вашем шаблоне:
/local/templates/<шаблон_сайта>/components/<пространство>/<имя>/<шаблон>/
Для bitrix:news.list с шаблоном .default в шаблоне сайта my_site это будет:
/local/templates/my_site/components/bitrix/news.list/.default/
Внутрь копируем содержимое исходной папки templates/.default: как минимум template.php, а если он есть — result_modifier.php и style.css. Дальше компонент при вызове с параметром "TEMPLATE" => ".default" подхватит именно вашу копию — приоритет у шаблона сайта, ядро остаётся нетронутым. Если нужно два разных вида — копируйте в две папки, например .default и cards, и переключайте параметром TEMPLATE.
Что менять в template.php: цикл по $arResult['ITEMS'], обёртки, вывод свойств
Файл template.php — это непосредственно HTML/PHP-шаблон, который отображает данные из $arResult. Ключевое место, которое вы почти всегда будете трогать, — цикл по элементам. Вот рабочий фрагмент, который можно положить в основу и адаптировать под свою вёрстку:
<?php
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
/** @var array $arParams */
/** @var array $arResult */
/** @var CBitrixComponentTemplate $this */
?>
<section class="news-list">
<?php foreach ($arResult["ITEMS"] as $arItem): ?>
<?php
$this->AddEditAction(
$arItem['ID'],
$arItem['EDIT_LINK'],
CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_EDIT")
);
$this->AddDeleteAction(
$arItem['ID'],
$arItem['DELETE_LINK'],
CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_DELETE"),
array("CONFIRM" => GetMessage('CT_BNL_ELEMENT_DELETE_CONFIRM'))
);
?>
<article class="news-item" id="<?= $this->GetEditAreaId($arItem['ID']); ?>">
<h3 class="news-item__title">
<a href="<?= $arItem["DETAIL_PAGE_URL"] ?>"><?= $arItem["NAME"] ?></a>
</h3>
<?php if ($arItem["PREVIEW_PICTURE"]["SRC"]): ?>
<img class="news-item__img"
src="<?= $arItem["PREVIEW_PICTURE"]["SRC"] ?>"
alt="<?= $arItem["NAME"] ?>">
<?php endif; ?>
<div class="news-item__text"><?= $arItem["PREVIEW_TEXT"] ?></div>
</article>
<?php endforeach; ?>
</section>Обёртки (<section>, <article>, классы) — целиком ваши: если в вёрстке итемы завёрнуты в три тега, оберните цикл в эти три тега, а не подгоняйте вёрстку под компонент. Вызовы AddEditAction / AddDeleteAction и GetEditAreaId нужны для режима правки в админке — без них кнопки редактирования элемента в публичной части не появятся. Их лучше не выкидывать, даже если сейчас режимом правки не пользуетесь: включите позже, а шаблон уже готов. Вывод полей (NAME, PREVIEW_PICTURE, PREVIEW_TEXT, DETAIL_PAGE_URL) — стандартный набор, но конкретные ключи зависят от того, что вернул компонент.
Как вывести пользовательские свойства элемента: $arItem['PROPERTIES']['AUTHOR']['VALUE']
Пользовательские свойства из инфоблока доступны через массив PROPERTIES внутри элемента. Синтаксис такой:
<?php if (!empty($arItem['PROPERTIES']['AUTHOR']['VALUE'])): ?>
<span class="news-item__author">
<?= $arItem['PROPERTIES']['AUTHOR']['VALUE'] ?>
</span>
<?php endif; ?>Внутри VALUE лежит значение, а в ~VALUE — «сырое», до обработки. Для свойства-списка VALUE вернёт код значения, а человекочитаемый текст — в VALUE_ENUM. Для множественного свойства VALUE будет массивом, и по нему нужно пройтись циклом. Проверку на непустоту делайте всегда — иначе в вёрстке появится пустой тег с нулевым содержимым.
Компонент news.list Битрикс: параметры, сортировка, постраничка
Самый частый запрос на эту тему звучит так: «взяли готовую вёрстку раздела, положили в инфоблок, а как теперь вывести список с постраничкой, чтобы ссылки были человеческие». bitrix:news.list — рабочая лошадка Bitrix Framework: на нём собирают новости, акции, статьи, отзывы, кейсы и вообще любой список элементов инфоблока. Компонент стандартный, входит в дистрибутив модуля, и его настройки уже покрывают большую часть того, что обычно просят: выбор полей элементов, управление постраничной навигацией, выбор формата даты, настройки кеширования.
Мы в проектах почти никогда не пишем свой компонент для списка. Сначала смотрим, хватает ли news.list. В девяти случаях из десяти хватает. Дальше уже вопрос шаблона, но об этом была предыдущая секция.
Ключевой момент, который стоит держать в голове: сортировка и постраничка задаются не в шаблоне, а в параметрах вызова. Именно поэтому важно понимать матрицу параметров, а не копировать вызов из чужого проекта наугад.
| Параметр | Что делает |
|---|---|
IBLOCK_ID |
ID инфоблока, из которого берём элементы |
PARENT_SECTION |
Ограничить выборку одним разделом |
ELEMENT_SORT_FIELD |
Поле сортировки: id, sort, name, active_from |
ELEMENT_SORT_ORDER |
Направление: asc или desc |
CACHE_TYPE |
Режим кеша: A — авто, N — выключен |
CACHE_TIME |
Время жизни кеша в секундах |
NEWPAGE_COUNT |
Сколько элементов на странице постранички |
Минимальный рабочий вызов с сортировкой по ID и постраничкой на 20 элементов выглядит так:
<?php
$APPLICATION->IncludeComponent(
"bitrix:news.list",
"custom.news",
[
"IBLOCK_ID" => 12, // замените на ваш IBLOCK_ID
"PARENT_SECTION" => false,
"ELEMENT_SORT_FIELD" => "id",
"ELEMENT_SORT_ORDER" => "desc",
"NEWPAGE_COUNT" => 20,
"CACHE_TYPE" => "A",
"CACHE_TIME" => 3600,
"SET_TITLE" => "N",
],
false
);
?>Чтобы получить человеческий URL вида /news/page2/ , нужно связать параметр с ЧПУ через urlrewrite.php . Обратите внимание: правило привязано к ID bitrix:news , а не к news.list напрямую. Причина в том, что ЧПУ обычно настраивается на комплексном компоненте, а news.list внутри уже подхватывает готовый PAGEN_1 . Если у вас список выводится отдельно, без обёртки bitrix:news , правила придётся прописывать вручную и следить, чтобы слеш в конце совпадал с тем, что генерирует шаблон навигации.
Грабли сортировки по свойству. Если указать в ELEMENT_SORT_FIELD пользовательское свойство (например, PROPERTY_PRICE), а само это свойство не добавлено в список сортировки инфоблока — сортировка молча не сработает. Компонент не выдаст ошибку, элементы просто останутся в порядке, который вернул инфоблок. Перед тем как ругаться на компонент, зайдите в настройки инфоблока и проверьте, что нужное свойство отмечено для сортировки. Это самая частая причина «сортировка не работает» в наших проектах.
Компонент каталога Битрикс: catalog.section.list и bitrix:catalog
Для вывода каталога одного компонента мало — их три, и каждый отвечает за свой уровень. catalog.section.list строит дерево разделов, bitrix:catalog — комплексная страница каталога с фильтром, сортировкой и детальной карточкой, а bitrix:catalog.section выводит список товаров одной категории. В проектах с каталогом на десятки тысяч SKU путаница между ними обходится дорого: неверный выбор даёт лишние SQL-запросы на каждый хит и заметно просаживает TTFB на листингах.
Разберём, что выбрать под конкретную задачу и как связать разделы со списком товаров, чтобы не собирать дерево категорий вручную в каждом шаблоне.
| Компонент | Что выводит | Когда использовать | Ключевые параметры |
|---|---|---|---|
bitrix:catalog.section.list |
Дерево или список разделов | Боковое меню категорий, плитка подкатегорий | IBLOCK_ID, SECTION_CODE, TOP_DEPTH |
bitrix:catalog.section |
Товары одной категории | Страница раздела с фильтром и постраничкой | SECTION_CODE, SECTION_ID, CACHE_TYPE |
bitrix:catalog |
Комплексный каталог целиком | Полноценный раздел с ЧПУ и карточками | SEF_MODE, IBLOCK_ID, USE_FILTER |
Для бокового меню категорий чаще всего берут catalog.section.list с шаблоном tree — он сам разворачивает вложенность и подсвечивает активный раздел:
<?php
$APPLICATION->IncludeComponent(
"bitrix:catalog.section.list",
"tree",
array(
"IBLOCK_TYPE" => "catalog",
"IBLOCK_ID" => 12, // замените на ID вашего инфоблока
"SECTION_ID" => "",
"SECTION_CODE" => $_REQUEST["SECTION_CODE"] ?? "",
"COUNT_ELEMENTS" => "Y",
"TOP_DEPTH" => "2",
"SECTION_URL" => "#SITE_DIR#/catalog/#SECTION_CODE#/",
"CACHE_TYPE" => "A",
"CACHE_TIME" => "36000000",
"CACHE_GROUPS" => "Y",
),
false
);
?>Ключевое здесь — связка через SECTION_CODE или SECTION_ID. Когда пользователь открывает страницу раздела, в $_REQUEST уже лежит код текущей категории; передаём его в catalog.section.list, и дерево подсвечивает нужный пункт, а не всегда показывает корень. Дальше этот же SECTION_CODE уходит в bitrix:catalog.section как PARENT_SECTION, и список товаров фильтруется по текущей категории без ручных условий в шаблоне.
Под свой проект мы обычно меняем три вещи: TOP_DEPTH (глубину вложенности — для магазинов с плоской структурой достаточно 1), COUNT_ELEMENTS (показывать ли число товаров рядом с категорией) и SECTION_URL под свой формат ЧПУ. Если разделов много и они редко меняются, оставляйте CACHE_TYPE => "A" — компонент закеширует выборку и не будет дёргать инфоблок на каждом хите.
С кешем у больших каталогов отдельная история. Кешировать стоит именно дерево разделов — оно статично и меняется только при импорте. А вот список товаров кешируют аккуратнее: при активном обмене с 1С кеш придётся сбрасывать после каждого импорта, иначе покупатель видит устаревшие цены и остатки. CACHE_GROUPS => "Y" держите включённым, если от группы пользователя зависят цены — иначе разные группы получат один и тот же закешированный вывод.
Как посадить готовый HTML-шаблон на компонент Битрикс
Типичный сценарий: дизайнер отдал вёрстку — например, owl-слайдер или сетку карточек, — а данные лежат в инфоблоке. Нужно «оживить» статичный HTML: подставить изображения, имена авторов, даты, ссылки. Первая мысль у многих — начать переименовывать классы в вёрстке под то, что генерирует компонент. Так делать не нужно. Компонент — это лишь источник данных, а разметку диктуете вы, а не наоборот. Мы в практике идём от обратного: берём готовый HTML как есть и оборачиваем в него цикл компонента, не трогая ни один существующий класс.
Вёрстка уже свёрстана и протестирована — она должна остаться неизменной. Компонент отдаёт массив $arResult['ITEMS'], а шаблон компонента решает, как этот массив разложить по уже готовой разметке. Если в исходном HTML слайды обёрнуты в три вложенных тега — значит, и цикл по элементам оборачиваем в те же три тега. Классы div-ов не подгоняем, а пишем новые, только если это реально нужно. Результат — вёрстка не поехала, а данные тянутся из инфоблока, и менеджер может добавлять слайды через админку.
- Создать инфоблок с нужными свойствами и заполнить тестовыми элементами. Под слайдер этого достаточно: свойство типа «Файл» для фонового изображения, свойство-строка для имени автора, свойство-дата для даты. Заведите 3–4 тестовых элемента — на них удобно проверять вывод, пока шаблон ещё не готов. Тип инфоблока можно назвать, например, «Слайдер», а сам инфоблок — «Слайды главной».
- Скопировать шаблон компонента в пользовательскую папку. Не правим ядро — копируем в
/local/templates/<шаблон_сайта>/components/bitrix/news.list/<имя>/. Здесь<имя>— произвольное название вашего шаблона, скажемmain-slider. Именно этот путь ищет Битрикс первым, поэтому ваши правки не затрутся при обновлении модуля. - В
template.phpобернуть цикл по элементам в теги из вёрстки. Открываемforeach ($arResult['ITEMS'] as $arItem)и внутрь кладём разметку одного слайда: картинку, имя автора, дату. Поля выводим через$arItem-PREVIEW_PICTUREдля изображения,PROPERTIES['AUTHOR']['VALUE']для автора. Не забывайте про$this->GetEditAreaId($arItem['ID'])— он даёт режим правки прямо из публичной части.
<section class="w3l-main-slider position-relative" id="home">
<div class="companies20-content">
<div class="owl-one owl-carousel owl-theme">
<?php foreach ($arResult['ITEMS'] as $arItem): ?>
<div id="<?= $this->GetEditAreaId($arItem['ID']); ?>">
<div class="slider-info banner-view bg bg2">
<img src="<?= $arItem['PREVIEW_PICTURE']['SRC']; ?>"
alt="<?= $arItem['PREVIEW_PICTURE']['ALT']; ?>">
<div class="banner-info">
<h3><?= $arItem['NAME']; ?></h3>
<p><?= $arItem['PROPERTIES']['AUTHOR']['VALUE']; ?></p>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</section>- JS-инициализацию слайдера вынести в отдельный
script.js. Инлайн-скрипт внутриtemplate.php— плохая идея: он выполнится до того, как DOM достроится, и сломает инициализацию. Положите файлscript.jsв папку шаблона и напишите в нём$(document).ready(function() { $(".owl-carousel").owlCarousel({ items: 1 }); });. Такой файл подключается автоматически для вашего шаблона.
result_modifier.php и component_epilog.php: зачем они нужны
Есть два файла, которые разработчики путают чаще всего — и эта путаница стоит часов отладки. result_modifier.php подключается до шаблона: он получает уже собранный $arResult из component.php и может его дообогатить — догрузить остатки, посчитать скидки, добавить поля, которых компонент не отдаёт по умолчанию. Шаблон (template.php) видит результат уже с этими правками.
component_epilog.php работает наоборот — после шаблона, и исполняется даже когда компонент отдан из кеша. Это его ключевая роль. Если данные нужно подставить в уже закешированный HTML, обычный result_modifier.php не поможет: при попадании в кеш он не выполняется. А component_epilog.php доступен с версии 9.0 именно как инструмент модификации работы компонента с включённым кешированием.
Понимание разницы экономит часы: вы сразу знаете, куда класть логику — в модификатор результата для подготовки данных или в эпилог для постобработки при кеше. И там же лежат грабли: если начать складывать в result_modifier.php десятки запросов и транзакций, это верный признак, что архитектура поехала не туда.
| Файл | Когда выполняется | Работает с кешем | Типичное применение |
|---|---|---|---|
result_modifier.php |
После component.php, до шаблона |
Нет — пропускается при кеше | Догрузка данных в $arResult |
component_epilog.php |
После template.php |
Да — исполняется всегда | Постобработка, подстановка при кеше |
template.php |
Между ними | Нет — рендер из кеша | Вывод HTML из $arResult |
Первый пример — догрузка остатков по списку ID прямо в result_modifier.php. Компонент вернул товары, но складских данных в них нет, а шаблону они нужны:
<?php
// result_modifier.php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
die();
}
$elementIds = [];
foreach ($arResult['ITEMS'] as $arItem) {
$elementIds[] = (int)$arItem['ID'];
}
if ($elementIds) {
$stocks = [];
$res = CIBlockElement::GetList(
[],
['ID' => $elementIds, 'IBLOCK_ID' => $arParams['IBLOCK_ID']],
false,
false,
['ID', 'PROPERTY_STOCK']
);
while ($row = $res->Fetch()) {
$stocks[(int)$row['ID']] = (int)$row['PROPERTY_STOCK_VALUE'];
}
foreach ($arResult['ITEMS'] as &$arItem) {
$arItem['STOCK'] = $stocks[(int)$arItem['ID']] ?? 0;
}
unset($arItem);
}Второй пример — component_epilog.php, который подставляет данные из модификатора уже после шаблона. Так делают, когда нужно переписать вывод в закешированном HTML:
<?php
// component_epilog.php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
die();
}
// $arResult здесь доступен — данные из result_modifier сохраняются
// через $this->arResult и приходят в эпилог.
$totalStock = 0;
foreach ($this->arResult['ITEMS'] as $arItem) {
$totalStock += (int)($arItem['STOCK'] ?? 0);
}
if ($totalStock === 0) {
// Заменим плейсхолдер в закешированном HTML на актуальный текст
$this->__component_epilog['NO_STOCK'] = true;
echo '<div class="bp-no-stock">Товары временно отсутствуют</div>';
}Почему компонент не срабатывает при AJAX и как это обойти
Ситуация, которая ломает привычную логику: компонент подключён, шаблон на месте, данные в инфоблоке есть, при загрузке страницы всё работает — а при AJAX-запросе тот же компонент возвращает пустоту или отдаёт данные без ваших обработчиков. У нас в практике такое всплывает, когда проект обрастает динамикой: фильтры каталога, «показать ещё», бесконечная прокрутка, обновление корзины без перезагрузки. Разработчик привык, что весь код подготовки живёт в component.php, и ожидает, что при AJAX он тоже отработает. Но ядро ведёт себя иначе.
При выполнении компонента в аяксовом режиме метод CBitrixComponent::executeComponent() не запускается. А именно этот метод отвечает за прогон component.php — того самого файла, где по традиции живёт вся бизнес-логика: инициализация переменных, выборки, подготовка $arResult. Раз executeComponent() не вызывается, содержимое component.php просто не исполняется. Это не баг и не регресс конкретной версии — штатное поведение ядра, зафиксированное в документации. Значит, его нужно не «чинить», а учитывать при проектировании компонента, который будет дергать AJAX.
Что конкретно ломается? Всё, что вы привыкли делать в component.php, при AJAX-вызове молча выпадает из процесса:
- Инициализация переменных и подготовка входных данных —
$arParamsприходят, но код, который их обрабатывает, не выполняется. - Регистрация обработчиков событий и подписка на хуки — колбэки не подключаются, события не ловятся.
- Подготовка
$arResultи дополнительные выборки — шаблон получает пустой или неполный массив. - Любая логика, завязанная на побочные эффекты: логирование, отправка писем, изменение состояния.
Шаблон рендерится, но данных в нём нет, или они не те, что при обычной загрузке. Отладка усложняется тем, что ошибок никто не бросает — код просто не выполняется, и вы ищете проблему в шаблоне, хотя она в архитектуре.
Штатных путей обойти это три, и выбирать между ними стоит по задаче:
- Вынести логику в
component_epilog.php— он подключается после исполнения шаблона и работает даже при включённом кешировании. Годится, когда нужно дотянуться до данных после рендера. - Сделать отдельный AJAX-контроллер в
/local/ajax/— свой обработчик, который принимает запрос, сам готовит данные и возвращает JSON. Самый гибкий вариант для нестандартных сценариев. - Использовать штатный AJAX-режим через
$APPLICATION->IncludeComponent()с параметромAJAX_MODE— если компонент поддерживает его из коробки.
Не пытайтесь решить это правкой ядра. Соблазн залезть в /bitrix/modules/ и дописать вызов executeComponent() вручную велик — особенно когда дедлайн. Но любые хаки в ядре слетят при первом же обновлении, а отлаживать потом придётся заново и в спешке. Штатных механизмов выше достаточно: component_epilog.php, отдельный контроллер и AJAX_MODE закрывают практически все реальные сценарии. Держите кастомную логику в /local/ — она переживёт обновления.
Как добавить подсказки к параметрам своего компонента
Свой компонент почти всегда пишется не «для себя», а для команды или для клиента, который потом сам будет его настраивать в визуальном редакторе. И здесь всплывает неочевидная проблема: параметры вы назвали осмысленно — SECTION_ID, ELEMENT_SORT_FIELD, CACHE_TIME, — а вот что именно вводить в поле, менеджер не понимает. В редакторе рядом с настройкой пусто, и человек либо лезет в документацию, либо звонит разработчику, либо оставляет дефолт и получает не тот вывод, который вы задумывали. У нас в практике это самая частая причина «компонент работает не так» уже после сдачи проекта — не баг, а непонятный параметр.
Решается это штатным механизмом ядра. Подсказки к параметрам создаются в языковой папке компонента /lang, в файле с массивом $MESS. Ключом выступает идентификатор параметра с суффиксом _TIP, значением — текст подсказки. Файл кладётся в подпапку с кодом языка (ru, en), и ядро само подтянет нужный в зависимости от языка интерфейса.
<?php
// /local/components/my/catalog.slider/lang/ru/.parameters.php
$MESS['MY_SLIDER_IBLOCK_ID_TIP'] = 'Инфоблок, из которого берутся слайды. Укажите ID или выберите из списка.';
$MESS['MY_SLIDER_SECTION_ID_TIP'] = 'Ограничить выборку одним разделом. Пусто — слайды со всего инфоблока.';
$MESS['MY_SLIDER_ELEMENT_COUNT_TIP'] = 'Сколько слайдов выводить. Для слайдера обычно 5–8, больше — тормозит загрузку.';
$MESS['MY_SLIDER_SORT_FIELD_TIP'] = 'Поле сортировки: SORT — ручной порядок в админке, ACTIVE_FROM — по дате начала активности.';
$MESS['MY_SLIDER_SORT_ORDER_TIP'] = 'Направление: ASC — по возрастанию, DESC — по убыванию.';
$MESS['MY_SLIDER_CACHE_TIME_TIP'] = 'Время кеша в секундах. 3600 — час, 0 — кеш отключён.';
$MESS['MY_SLIDER_SHOW_PREVIEW_TIP'] = 'Показывать описание слайда под заголовком. Отключите, если в макете его нет.';Ключевое здесь — суффикс _TIP и совпадение префикса с идентификатором параметра из .parameters.php. Ядро сопоставляет их по имени: параметр SECTION_ID ищет подсказку в ключе SECTION_ID_TIP. Регистрировать эти строки в .parameters.php не нужно — достаточно, чтобы файл лежал в языковой папке рядом с описанием параметров.
Отдельно стоит понимать, что массив $MESS в языковой папке — это ещё и псевдонимы переменных. Ядро объединяет массив псевдонимов по умолчанию и псевдонимы, переданные во входных параметрах компонента, в один массив. И вот тут важная деталь: если псевдоним определён и в массиве по умолчанию, и во входных параметрах, побеждает значение из входных параметров. То есть при вызове через IncludeComponent вы можете переопределить подпись или подсказку на лету — не трогая файл компонента. Это удобно, когда один и тот же компонент используется в двух разделах и в каждом параметр называется по-своему: SORT_FIELD в одном месте логичнее подписать «Порядок в каталоге», в другом — «Сортировка новостей».
Какие места читатель чаще всего захочет поменять под свой проект: тексты подсказок (пишите их языком менеджера, а не разработчика), набор языков в /lang, и — если компонент сложный — группировку параметров. По нашему опыту, стоит держать подсказки короткими: одна-две строки, конкретный пример значения. «Укажите ID инфоблока» полезнее, чем «Здесь задаётся идентификатор инфоблока, из которого производится выборка элементов».
Самые частые ошибки при работе с компонентами Битрикс
Типичная картина: сайт запущен, всё работает, а через полгода выясняется, что половина правок живёт в /bitrix/components/bitrix, кеш никто не сбрасывает, а шаблон компонента правили сразу в трёх местах. Мы собрали грабли, которые встречаем чаще всего в проектах на поддержке. Не все они про код. Значительная часть про процесс и привычки: где хранить кастомизацию, когда сбрасывать кеш, почему правка «на живую» обходится дороже, чем кажется. Разберём их по порядку — от самых разрушительных к самым безобидным.
Большинство ошибок объединяет одна причина: разработчик не отделяет ядро от пользовательской части. Ядро обновляется, пользовательские файлы — нет. Как только эта граница размывается, проект начинает терять правки при каждом обновлении платформы.
Ниже — матрица, с которой удобно сверяться перед кастомизацией.
| Грабля | Причина | Как исправить |
|---|---|---|
Правки прямо в /bitrix/components/bitrix |
Кажется быстрее, чем копировать шаблон | Шаблон — в /local/templates |
| Забыли сбросить кеш после правки | Кеш компонента помнит старое | Сброс кеша в админке или ?clear_cache=Y |
Логика в template.php |
Лень выносить в result_modifier.php |
Данные — в модификатор, вывод — в шаблон |
| Компонент не отвечает на AJAX | executeComponent() не запускается в аяксе |
Отдавать данные через отдельный обработчик |
| Свойство не выводится в шаблоне | Не указано в параметрах вызова | Добавить свойство в вызов компонента |
Разобранные случаи на ru.stackoverflow: ru.stackoverflow, ru.stackoverflow.
Читайте также: Обмен Битрикс24 и 1С: как настроить синхронизацию и не сломать каталог.
Частые вопросы
Можно ли переопределить шаблон компонента только для одной страницы, не трогая остальные?
Да, передайте в $APPLICATION->IncludeComponent() параметр TEMPLATE с именем нужного шаблона, например "news.list" → TEMPLATE => "custom". Тогда на этой странице подставится templates/custom, а на остальных останется дефолтный.
Чем отличается result_modifier.php от component_epilog.php?
result_modifier.php выполняется до подключения шаблона и меняет $arResult, который увидит шаблон. component_epilog.php выполняется уже после отрисовки шаблона, работает с $arParams и $arResult и подходит для подключения скриптов, метатегов или кэшируемых доп. запросов.
Что делать, если компонент не срабатывает при AJAX-запросе?
Скорее всего запрос идёт в обход пролога или без подключения модуля — проверьте, что в AJAX-обработчике есть require $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php". Также убедитесь, что не мешает component_epilog.php с кэшем: при AJAX передавайте $arParams["CACHE_TYPE"] => "N".
А если у меня готовый HTML-шаблон на Bootstrap, как его посадить на bitrix:catalog без переписывания вёрстки?
Скопируйте шаблон компонента в /local/templates/ваш_шаблон/components/bitrix/catalog/.template/.default/ и замените статичные блоки на вывод из $arResult: SECTION_LIST, ITEMS, PRICES. Разметку и классы Bootstrap оставьте как есть — компонент отдаёт данные, а не HTML.
Можно ли добавить свои параметры в существующий компонент, не правя ядро?
Да, через .parameters.php в копии шаблона компонента в /local/. Добавьте ключ в массив $arParams и читайте его в result_modifier.php или шаблоне как $arParams["MY_PARAM"]. Правки в /bitrix/components/.../ затрёт обновление.
Чем отличается catalog.section.list от bitrix:catalog и когда что использовать?
catalog.section.list выводит только список разделов каталога (дерево или плоский), без товаров. bitrix:catalog — это комплексный компонент, который сам роутит на section.list, section, element и detail в зависимости от URL. Для страницы со списком разделов берите первый, для полноценного каталога с ЧПУ — второй.