Штатные компоненты Битрикс закрывают 80% задач. Но когда нужна нестандартная логика — вывод данных из нескольких инфоблоков с фильтрацией, кастомная пагинация, интеграция с внешним API — приходится писать свой компонент. Показываем пошаговый процесс создания компонента с нуля: от файловой структуры до кеширования.
Файловая структура компонента
Компонент создаём в папке /local/components/ — не в /bitrix/components/, чтобы не потерять при обновлении:
/local/components/mycompany/news.list/
├── .description.php # Описание для визуального редактора
├── .parameters.php # Настраиваемые параметры
├── class.php # Основная логика (ООП)
├── templates/
│ └── .default/
│ ├── template.php # Шаблон вывода
│ ├── result_modifier.php # Модификация данных перед выводом
│ ├── component_epilog.php # Код после вывода (вне кеша)
│ └── style.css
└── lang/
└── ru/
├── .description.php
└── .parameters.php
Пространство имён mycompany — обязательно. Без него Битрикс не найдёт компонент.
.description.php — регистрация компонента
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
$arComponentDescription = [
'NAME' => 'Список новостей',
'DESCRIPTION' => 'Выводит список новостей из инфоблока с фильтрацией и пагинацией',
'ICON' => '/images/icon.gif',
'SORT' => 10,
'CACHE_PATH' => 'Y',
'PATH' => [
'ID' => 'mycompany',
'NAME' => 'Мои компоненты',
'CHILD' => [
'ID' => 'content',
'NAME' => 'Контент',
],
],
];
.parameters.php — параметры компонента
Параметры — это то, что редактор сайта настраивает в визуальном редакторе:
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
$arIBlockTypes = CIBlockParameters::GetIBlockTypes();
$arIBlocks = [];
$rsIBlocks = \Bitrix\Iblock\IblockTable::getList([
'filter' => ['ACTIVE' => 'Y'],
'select' => ['ID', 'NAME'],
'order' => ['SORT' => 'ASC'],
]);
while ($row = $rsIBlocks->fetch()) {
$arIBlocks[$row['ID']] = '[' . $row['ID'] . '] ' . $row['NAME'];
}
$arComponentParameters = [
'GROUPS' => [
'SETTINGS' => [
'NAME' => 'Основные настройки',
'SORT' => 100,
],
'PAGER' => [
'NAME' => 'Пагинация',
'SORT' => 200,
],
],
'PARAMETERS' => [
'IBLOCK_TYPE' => [
'PARENT' => 'SETTINGS',
'NAME' => 'Тип инфоблока',
'TYPE' => 'LIST',
'VALUES' => $arIBlockTypes,
'REFRESH' => 'Y',
],
'IBLOCK_ID' => [
'PARENT' => 'SETTINGS',
'NAME' => 'Инфоблок',
'TYPE' => 'LIST',
'VALUES' => $arIBlocks,
'REFRESH' => 'Y',
],
'NEWS_COUNT' => [
'PARENT' => 'PAGER',
'NAME' => 'Количество на странице',
'TYPE' => 'STRING',
'DEFAULT' => '10',
],
'SORT_BY' => [
'PARENT' => 'SETTINGS',
'NAME' => 'Сортировка по',
'TYPE' => 'LIST',
'VALUES' => [
'ACTIVE_FROM' => 'Дате активности',
'SORT' => 'Индексу сортировки',
'NAME' => 'Названию',
],
'DEFAULT' => 'ACTIVE_FROM',
],
'SORT_ORDER' => [
'PARENT' => 'SETTINGS',
'NAME' => 'Направление сортировки',
'TYPE' => 'LIST',
'VALUES' => [
'DESC' => 'По убыванию',
'ASC' => 'По возрастанию',
],
'DEFAULT' => 'DESC',
],
'CACHE_TIME' => ['DEFAULT' => 3600],
],
];
class.php — основная логика
Современный подход — ООП через наследование от CBitrixComponent:
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
use Bitrix\Main\Loader;
use Bitrix\Main\ArgumentException;
use Bitrix\Iblock\ElementTable;
class MyCompanyNewsList extends CBitrixComponent
{
private int $iblockId;
private int $pageSize;
public function onPrepareComponentParams($arParams): array
{
$arParams['IBLOCK_ID'] = (int)($arParams['IBLOCK_ID'] ?? 0);
$arParams['NEWS_COUNT'] = (int)($arParams['NEWS_COUNT'] ?? 10);
$arParams['SORT_BY'] = $arParams['SORT_BY'] ?? 'ACTIVE_FROM';
$arParams['SORT_ORDER'] = $arParams['SORT_ORDER'] ?? 'DESC';
$arParams['CACHE_TIME'] = (int)($arParams['CACHE_TIME'] ?? 3600);
return $arParams;
}
public function executeComponent(): void
{
if (!Loader::includeModule('iblock')) {
ShowError('Модуль iblock не установлен');
return;
}
$this->iblockId = $this->arParams['IBLOCK_ID'];
$this->pageSize = $this->arParams['NEWS_COUNT'];
if ($this->iblockId <= 0) {
ShowError('Не указан инфоблок');
return;
}
if ($this->startResultCache()) {
$this->fetchData();
if (empty($this->arResult['ITEMS'])) {
$this->abortResultCache();
}
$this->includeComponentTemplate();
}
}
private function fetchData(): void
{
$navParams = \CDBResult::GetNavParams([
'nPageSize' => $this->pageSize,
'bDescPageNumbering' => false,
]);
$arFilter = [
'IBLOCK_ID' => $this->iblockId,
'ACTIVE' => 'Y',
];
$arSort = [
$this->arParams['SORT_BY'] => $this->arParams['SORT_ORDER'],
];
$rsElements = \CIBlockElement::GetList(
$arSort,
$arFilter,
false,
['nPageSize' => $this->pageSize, 'iNumPage' => $navParams['PAGEN']],
['ID', 'NAME', 'DETAIL_PAGE_URL', 'PREVIEW_TEXT', 'PREVIEW_PICTURE', 'ACTIVE_FROM']
);
$this->arResult['ITEMS'] = [];
while ($arElement = $rsElements->GetNext()) {
if ($arElement['PREVIEW_PICTURE']) {
$arElement['PREVIEW_PICTURE'] = \CFile::GetFileArray($arElement['PREVIEW_PICTURE']);
}
$this->arResult['ITEMS'][] = $arElement;
}
$this->arResult['NAV_STRING'] = $rsElements->GetPageNavString(
'Новости',
'default',
true
);
$this->arResult['NAV_RESULT'] = $rsElements;
}
}
Важные моменты:
onPrepareComponentParams— валидация и приведение типов параметров. Вызывается до кеширования.startResultCache— проверяет, есть ли кеш. Если да — сразу отдаёт шаблон из кеша,fetchDataне вызывается.abortResultCache— если данных нет, не кешируем пустой результат.
template.php — шаблон вывода
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
/** @var array $arResult */
/** @var array $arParams */
?>
<div class="news-list">
<?php foreach ($arResult['ITEMS'] as $arItem): ?>
<article class="news-list__item">
<?php if (!empty($arItem['PREVIEW_PICTURE'])): ?>
<div class="news-list__image">
<img
src="<?= $arItem['PREVIEW_PICTURE']['SRC'] ?>"
alt="<?= htmlspecialchars($arItem['NAME']) ?>"
loading="lazy"
width="<?= $arItem['PREVIEW_PICTURE']['WIDTH'] ?>"
height="<?= $arItem['PREVIEW_PICTURE']['HEIGHT'] ?>"
>
</div>
<?php endif; ?>
<div class="news-list__content">
<?php if ($arItem['ACTIVE_FROM']): ?>
<time class="news-list__date" datetime="<?= $arItem['ACTIVE_FROM'] ?>">
<?= FormatDate('d F Y', MakeTimeStamp($arItem['ACTIVE_FROM'])) ?>
</time>
<?php endif; ?>
<h3 class="news-list__title">
<a href="<?= $arItem['DETAIL_PAGE_URL'] ?>">
<?= $arItem['NAME'] ?>
</a>
</h3>
<?php if ($arItem['PREVIEW_TEXT']): ?>
<div class="news-list__text">
<?= $arItem['PREVIEW_TEXT'] ?>
</div>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
<?php if ($arResult['NAV_STRING']): ?>
<div class="news-list__pagination">
<?= $arResult['NAV_STRING'] ?>
</div>
<?php endif; ?>
</div>
result_modifier.php — модификация данных
Этот файл выполняется после class.php, но до вывода шаблона. Работает внутри кеша. Идеально для трансформации данных без изменения логики компонента:
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
// Добавляем ресайзы картинок
foreach ($arResult['ITEMS'] as &$arItem) {
if (!empty($arItem['PREVIEW_PICTURE'])) {
$arItem['PREVIEW_PICTURE_RESIZE'] = CFile::ResizeImageGet(
$arItem['PREVIEW_PICTURE']['ID'],
['width' => 400, 'height' => 300],
BX_RESIZE_IMAGE_PROPORTIONAL,
true
);
}
// Форматируем дату
if ($arItem['ACTIVE_FROM']) {
$arItem['DATE_FORMATTED'] = FormatDate(
'd F Y',
MakeTimeStamp($arItem['ACTIVE_FROM'])
);
}
}
unset($arItem);
component_epilog.php — код вне кеша
Выполняется всегда, даже когда компонент отдаёт данные из кеша. Нужен для:
- Счётчиков просмотров
- Добавления в навигационную цепочку
- Логики, зависящей от текущего пользователя
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
// Навигационная цепочка — вне кеша, т.к. зависит от текущей страницы
$APPLICATION->AddChainItem('Новости', '/news/');
// Заголовок страницы
$APPLICATION->SetTitle('Новости компании');
Вызов компонента на странице
<?php $APPLICATION->IncludeComponent(
'mycompany:news.list',
'.default',
[
'IBLOCK_ID' => 5,
'NEWS_COUNT' => 12,
'SORT_BY' => 'ACTIVE_FROM',
'SORT_ORDER' => 'DESC',
'CACHE_TIME' => 3600,
'CACHE_TYPE' => 'A',
]
); ?>
Частые ошибки
- Логика в template.php — запросы к базе в шаблоне замедляют отдачу кеша. Вся работа с данными — в
class.php. - Нет
abortResultCache— без этого закешируется пустой результат, и даже после появления данных будет показываться пустота. - Забыли
unsetпослеforeachпо ссылке — классический PHP-баг, когда&$arItemв цикле портит последний элемент массива. - Кастомные CSS/JS подключают в template.php — лучше использовать
\Bitrix\Main\Page\Assetвcomponent_epilog.php.
Есть идея? Реализуем
Разрабатываем проекты, которые решают задачи бизнеса — от лендинга до сложного сервиса. Расскажите о своей задаче, подберём решение.

