К содержимому

1С-Битрикс: создаём свой компонент с классом и кэшированием

Компонент — основной способ вывести данные на страницу в Битриксе. Стандартных компонентов много, но рано или поздно понадобится свой: стандартный делает лишнее, не умеет нужного, а копировать и править его неудобно — при обновлении правки потеряются.

Свои компоненты кладём в /local/components/, а не в /bitrix/components/: папку /bitrix перезаписывают обновления.

Будем делать компонент devlib:news.latest — список последних новостей из инфоблока.

/local/components/devlib/news.latest/
├── .description.php     — название и место в визуальном редакторе
├── .parameters.php      — настройки компонента
├── class.php            — логика
├── lang/ru/...          — языковые файлы (по желанию)
└── templates/
    └── .default/
        ├── template.php — вёрстка
        └── style.css    — подключится автоматически
Задача 1 Средняя

Описать компонент

Сделайте так, чтобы компонент появился в визуальном редакторе в отдельной группе «DevLib».

Показать решение Скрыть решение
.description.php
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}

$arComponentDescription = [
    'NAME'        => 'Последние новости',
    'DESCRIPTION' => 'Выводит N последних активных элементов инфоблока',
    'PATH'        => [
        'ID'   => 'devlib',
        'NAME' => 'DevLib',
    ],
    'CACHE_PATH'  => 'Y',
];

Строка с B_PROLOG_INCLUDED есть в каждом файле компонента: она запрещает открыть файл напрямую по URL.

Задача 2 Средняя

Добавить параметры

Компонент должен принимать ID инфоблока, количество элементов и время кэширования.

Показать решение Скрыть решение
.parameters.php
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}

$arComponentParameters = [
    'PARAMETERS' => [
        'IBLOCK_ID' => [
            'PARENT'  => 'BASE',
            'NAME'    => 'ID инфоблока',
            'TYPE'    => 'STRING',
            'DEFAULT' => '',
        ],
        'COUNT' => [
            'PARENT'  => 'BASE',
            'NAME'    => 'Количество элементов',
            'TYPE'    => 'STRING',
            'DEFAULT' => '5',
        ],
        'CACHE_TIME' => ['DEFAULT' => 3600],
    ],
];

CACHE_TIME — системный параметр: Битрикс сам добавит его в группу настроек кэша.

Задача 3 Средняя

Написать логику в class.php

Получите элементы, сохраните в $arResult и подключите шаблон. Результат должен кэшироваться.

Показать решение Скрыть решение
class.php
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}

use Bitrix\Main\Application;
use Bitrix\Main\Loader;
use Bitrix\Iblock\ElementTable;

class DevlibNewsLatestComponent extends CBitrixComponent
{
    /** Проверка и нормализация параметров — до кэша */
    public function onPrepareComponentParams($arParams): array
    {
        $arParams['IBLOCK_ID']  = (int) ($arParams['IBLOCK_ID'] ?? 0);
        $arParams['COUNT']      = max(1, (int) ($arParams['COUNT'] ?? 5));
        $arParams['CACHE_TIME'] = (int) ($arParams['CACHE_TIME'] ?? 3600);

        return $arParams;
    }

    public function executeComponent(): void
    {
        if ($this->arParams['IBLOCK_ID'] <= 0) {
            ShowError('Не указан ID инфоблока');
            return;
        }

        // startResultCache: true — кэша нет, надо собрать данные; false — данные из кэша
        if ($this->startResultCache()) {
            if (!Loader::includeModule('iblock')) {
                $this->abortResultCache();
                ShowError('Модуль «Информационные блоки» не установлен');
                return;
            }

            $this->arResult['ITEMS'] = $this->getItems();

            if (!$this->arResult['ITEMS']) {
                $this->abortResultCache(); // пустой результат не кэшируем
            }

            // Тегированный кэш: модуль iblock сбрасывает тег iblock_id_N
            // при добавлении, изменении и удалении элементов
            Application::getInstance()->getTaggedCache()
                ->registerTag('iblock_id_' . $this->arParams['IBLOCK_ID']);

            $this->includeComponentTemplate();
        }
    }

    private function getItems(): array
    {
        $rows = ElementTable::getList([
            'select' => ['ID', 'NAME', 'CODE', 'PREVIEW_TEXT', 'ACTIVE_FROM'],
            'filter' => [
                '=IBLOCK_ID' => $this->arParams['IBLOCK_ID'],
                '=ACTIVE'    => 'Y',
            ],
            'order'  => ['ACTIVE_FROM' => 'DESC', 'ID' => 'DESC'],
            'limit'  => $this->arParams['COUNT'],
        ])->fetchAll();

        foreach ($rows as &$row) {
            $row['DATE'] = $row['ACTIVE_FROM']
                ? $row['ACTIVE_FROM']->format('d.m.Y')
                : '';
        }

        return $rows;
    }
}

Как работает кэш компонента:

  • ключ кэша строится из параметров компонента и шаблона, поэтому два вызова с разным IBLOCK_ID кэшируются отдельно;
  • всё, что выполняется внутри if ($this->startResultCache()), при попадании в кэш не выполняется — включая шаблон. Вывод шаблона кэшируется целиком;
  • запросы через ORM сами теги не регистрируют, поэтому тег iblock_id_N добавляем вручную. Если на сайте включено управляемое (тегированное) кэширование, кэш компонента сбросится при изменении элементов инфоблока. Иначе — по истечении CACHE_TIME.
Задача 4 Средняя

Сверстать шаблон

Компонент кладёт новости в $arResult['ITEMS'], у каждой есть NAME, CODE, PREVIEW_TEXT и DATE (может быть пустой). Сверстайте шаблон .default: для каждой новости — дата (если есть), заголовок-ссылка на /news/{CODE}/ и анонс.

Данные приходят из базы, их заполняют редакторы — выводите их так, чтобы через название новости нельзя было внедрить HTML или скрипт.

Показать решение Скрыть решение
templates/.default/template.php
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
/** @var array $arResult */
?>
<div class="news-latest">
    <?php foreach ($arResult['ITEMS'] as $item): ?>
        <article class="news-latest__item">
            <?php if ($item['DATE']): ?>
                <time class="news-latest__date"><?= $item['DATE'] ?></time>
            <?php endif; ?>
            <h3 class="news-latest__title">
                <a href="/news/<?= htmlspecialcharsbx($item['CODE']) ?>/">
                    <?= htmlspecialcharsbx($item['NAME']) ?>
                </a>
            </h3>
            <div class="news-latest__text"><?= $item['PREVIEW_TEXT'] ?></div>
        </article>
    <?php endforeach; ?>
</div>

htmlspecialcharsbx — функция Битрикса для экранирования, используйте её для всего, что ввёл пользователь. PREVIEW_TEXT здесь выводится без экранирования, потому что редактор в админке сохраняет его как HTML. Если в инфоблоке тип текста «text», экранируйте и его.

Файл style.css из папки шаблона Битрикс подключит сам.

Задача 5 Средняя

Вызвать компонент на странице

Выведите на главной странице сайта (/index.php) три последние новости из инфоблока с ID 1. Шаблон — по умолчанию, кэш — на час, с учётом общих настроек кэширования сайта.

Показать решение Скрыть решение
/index.php
<?php
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/header.php';
$APPLICATION->SetTitle('Главная');
?>

<?php $APPLICATION->IncludeComponent(
    'devlib:news.latest',
    '',                       // пустая строка = шаблон .default
    [
        'IBLOCK_ID'  => 1,
        'COUNT'      => 3,
        'CACHE_TYPE' => 'A',  // A — авто (по настройкам сайта), Y — всегда, N — никогда
        'CACHE_TIME' => 3600,
    ]
); ?>

<?php require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/footer.php'; ?>
Задача 6 Средняя

Добавить второй шаблон без копирования логики

Нужно вывести те же новости в подвале сайта компактным списком.

Показать решение Скрыть решение

Логика остаётся в class.php, добавляем только новую папку шаблона:

templates/compact/template.php
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
?>
<ul class="news-compact">
    <?php foreach ($arResult['ITEMS'] as $item): ?>
        <li><a href="/news/<?= htmlspecialcharsbx($item['CODE']) ?>/"><?= htmlspecialcharsbx($item['NAME']) ?></a></li>
    <?php endforeach; ?>
</ul>
Вызов с шаблоном compact
$APPLICATION->IncludeComponent('devlib:news.latest', 'compact', ['IBLOCK_ID' => 1, 'COUNT' => 5]);

Шаблон можно переопределить и на уровне шаблона сайта: файл /local/templates/<шаблон_сайта>/components/devlib/news.latest/compact/template.php имеет приоритет над шаблоном внутри компонента.

Итог

  • Свои компоненты — в /local/components/<пространство>/<имя>/.
  • Логика — в class.php (наследник CBitrixComponent), вёрстка — в шаблонах.
  • Всё, что должно кэшироваться, — внутри startResultCache(). Пустой или ошибочный результат — abortResultCache().
  • Вывод данных — через htmlspecialcharsbx.