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

Вызов компонента внутри компонента в Битрикс: рабочие способы

Вызов компонента внутри компонента в Битрикс - рабочий приём, к которому я обращаюсь каждый раз, когда логика проекта не укладывается в один шаблон: нужно вставить блок похожих товаров в детальную страницу каталога, вывести форму заявки внутри новостной ленты или подключить счётчик корзины в шапке, которая сама рисуется отдельным компонентом. За годы работы с Битрикс я перепробовал три способа это сделать, и не все из них одинаково безопасны для кэша и производительности проекта - ниже разбираю каждый с реальными примерами и граблями, на которые сам наступал.

Зачем вообще вкладывать один компонент в другой

Компонентная архитектура Битрикс построена так, что каждый компонент - законченный модуль со своим component.php, шаблоном и (опционально) кэшем. Но реальные страницы редко состоят из одного блока. Карточка товара тянет за собой блок “с этим товаром покупают”, детальная страница новости - похожие материалы, форма заказа на checkout - калькулятор доставки СДЭК как отдельный компонент, который считает стоимость через API и обновляется без перезагрузки страницы.

Можно слепить всю логику в один component.php, но тогда теряется вся выгода от модульности: нельзя переиспользовать блок на другой странице, сложнее тестировать, а любое обновление модуля каталога может сломать самописную мешанину. Вложенный вызов компонента решает это чище - каждый блок остаётся самостоятельным, но собирается в общий вывод программно.

Способ 1 - IncludeComponent прямо в шаблоне компонента

Самый частый случай: внутри component_template.php одного компонента вызывается второй через тот же $APPLICATION->IncludeComponent, что используется в обычных шаблонах сайта. Никакой магии - движок не делает разницы между вызовом из шаблона страницы и вызовом из шаблона компонента.

// внутри catalog.element/.default/template.php
$APPLICATION->IncludeComponent(
    "bitrix:news.list",
    "related_items",
    array(
        "IBLOCK_TYPE" => "content",
        "IBLOCK_ID" => $arResult["IBLOCK_ID"],
        "NEWS_COUNT" => 4,
        "SORT_BY1" => "ACTIVE_FROM",
        "SORT_ORDER1" => "DESC",
        "CACHE_TYPE" => "A",
        "CACHE_TIME" => "3600",
    ),
    false
);

Так я обычно выношу блоки “похожие товары”, “новости по разделу”, виджеты в сайдбаре. Работает предсказуемо, отлаживается как обычный компонент - если что-то не выводится, проверяешь его отдельно, вне контекста родителя.

Способ 2 - наследование кэша и параметров через четвёртый аргумент

Здесь начинаются нюансы, из-за которых половина статей в интернете про вложенные компоненты обходит стороной вопрос кэша. Последний параметр false в примере выше - это как раз ссылка на родительский компонент. Если передать туда объект $component (доступен в шаблоне компонента как $this->__component или через глобальную переменную), дочерний компонент встраивается в кэш родителя, а не создаёт свой отдельный файл кэша.

global $component;

$APPLICATION->IncludeComponent(
    "bitrix:news.list",
    "related_items",
    array(
        "IBLOCK_TYPE" => "content",
        "IBLOCK_ID" => $arResult["IBLOCK_ID"],
        "NEWS_COUNT" => 4,
        "CACHE_TYPE" => "A",
        "CACHE_TIME" => "3600",
    ),
    $component,
    array("HIDE_ICONS" => "Y")
);

Разница на практике ощутимая: без передачи родителя на странице с CACHE_TYPE=A у вас накопится куча независимых кэш-файлов для вложенных блоков, каждый со своим таймером жизни, и инвалидация кэша родителя (например, по событию OnAgentAfterAddNewsItem) перестанет чистить вложенные данные. Я как-то потратил часа два, выясняя, почему обновлённая новость в блоке “похожие” не подтягивается - при этом сам родительский компонент кэш чистил исправно. Причина была ровно в этом: дочерний компонент жил своей кэш-жизнью.

Пятый параметр HIDE_ICONS

Массив array(“HIDE_ICONS” => “Y”) в примере выше убирает иконки редактирования компонента в панели администратора для вложенного блока - без этого при заходе в режим правки страницы вложенный компонент рисует свою рамку редактирования поверх родительской, и получается визуальная каша из двух наложенных панелей.

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

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

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

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

Способ 3 - вызов бизнес-логики компонента без рендера шаблона

Иногда нужен не вывод компонента, а именно его логика - например, дёрнуть метод получения данных из catalog.section, не рисуя HTML вообще, чтобы отдать JSON в AJAX-обработчик. Для этого в Битрикс есть CBitrixComponent::includeComponentClass, который подключает класс компонента без выполнения полного цикла executeComponent.

CBitrixComponent::includeComponentClass("bitrix:catalog.section");

$component = new CatalogSectionComponent();
$component->arParams = $arParams;
$component->initComponent("bitrix:catalog.section", ".default");
$arResult = $component->getResult(); // условно, зависит от версии компонента

На практике я использую этот путь редко и только когда точно знаю внутреннее устройство конкретного компонента (методы могут отличаться версия от версии), обычно это самописные компоненты, где я сам проектирую class-based API для переиспользования логики между несколькими точками входа - HTML-страницей и AJAX-хендлером. Для чужих модульных компонентов (маркетплейс, инфоблоки) я такое не делаю - слишком велик риск сломаться на обновлении модуля.

Кэширование вложенных компонентов - где реально теряются данные

Три сценария, с которыми сталкивался на живых проектах:

  • Родитель кэшируется, ребёнок - динамический блок. Счётчик товаров в корзине внутри шапки не должен попадать под общий кэш меню. Решение - либо CACHE_TYPE=“N” для дочернего компонента, либо вынос его в component_epilog.php родителя, который выполняется уже после формирования кэшируемой части страницы.
  • Composite-кэш и динамические зоны. Если на проекте включено композитное кэширование всей страницы, вложенный компонент с персональными данными (имя пользователя, статус заказа) обязан быть оформлен как динамическая зона - иначе один пользователь увидит закэшированные данные другого. Это отдельная тема, но именно вложенные компоненты - самое частое место, где про неё забывают.
  • Двойной вызов одного компонента на странице. Если один и тот же компонент с одинаковыми параметрами вызывается дважды (например, форма заявки и в шапке, и в футере), Битрикс может закэшировать оба вызова в один файл, если не различать их через CACHE_TYPE или уникальный набор параметров кэширования - на выходе получаешь одинаковый результат в обоих местах, даже если по логике они должны отличаться.

Динамический вызов через AJAX без перезагрузки страницы

Для фильтров каталога, пагинации без reload, кнопки “показать ещё” вложенный компонент часто нужно перерисовывать отдельно от родителя. Здесь я обычно поднимаю AJAX-обработчик, который дёргает $APPLICATION->IncludeComponent тем же способом, что и в обычном шаблоне, но с параметром AJAX_MODE и заворачиваю результат через ob_start()/ob_get_clean(), чтобы вернуть готовый HTML-фрагмент клиенту.

ob_start();
$APPLICATION->IncludeComponent(
    "bitrix:catalog.section",
    "ajax_grid",
    array(
        "IBLOCK_ID" => $iblockId,
        "SECTION_ID" => $sectionId,
        "PAGE" => $page,
        "CACHE_TYPE" => "N",
    )
);
$html = ob_get_clean();

echo json_encode(array("html" => $html), JSON_UNESCAPED_UNICODE);

Такую связку я обычно собираю вместе с фронтовой частью на чистом JS или с интеграцией в существующий стек проекта - если в компании уже есть готовые сниппеты под похожие задачи, проще стартовать не с нуля, а с проверенного каркаса - у меня в библиотеке готовых скриптов есть несколько таких заготовок под AJAX-подгрузку вложенных блоков в Битрикс.

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

Ошибка Последствие Как чинить
Не передан родительский компонент 4‑м параметром Дочерний компонент кэшируется отдельно, не чистится вместе с родителем Передавать $component/$this->__component
Персональные данные во вложенном блоке при включённом composite-кэше Один пользователь видит чужие данные из кэша Оформить блок как динамическую зону композита
Рекурсивный вызов компонента самого себя без ограничения по вложенности Падение по памяти или таймауту PHP Ограничивать глубину явным счётчиком в $arParams
Одинаковый компонент дважды на странице без разделения кэша Оба места показывают одинаковый (неверный) результат Разные CACHE_TYPE/уникальные параметры на каждый вызов

Ещё один момент, который редко упоминают: вложенный компонент наследует не все глобальные переменные автоматически. Если родитель что-то положил в $arResult до вызова дочернего, это не «протекает» само собой в параметры ребёнка - всё, что нужно передать, придётся явно прописать в массиве $arParams при вызове IncludeComponent. На проектах, где заказчик просит именно доработку существующей структуры компонентов, я обычно начинаю с аудита текущих вложенных вызовов - процентов 60 проблем с “то работает, то нет” на Битрикс-проектах упираются именно в кэш вложенных блоков.

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

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

от 15 000 ₽/мес

Подробнее →

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

Можно ли вызвать компонент внутри компонента без вывода шаблона, только ради данных?

Да, через CBitrixComponent::includeComponentClass с последующей инициализацией класса вручную, без вызова executeComponent целиком. Это подходит для собственных компонентов, где вы контролируете внутреннее API класса. Для чужих модульных компонентов надёжнее вызывать обычным IncludeComponent и парсить результат из $arResult, потому что внутренняя структура методов у сторонних компонентов может измениться при обновлении модуля.

Как избежать двойного кэширования при вложенных компонентах?

Передавайте объект родительского компонента четвёртым параметром в $APPLICATION->IncludeComponent. Тогда дочерний блок попадает в кэш-контекст родителя, и очистка кэша родительского компонента автоматически захватывает вложенный. Без этого параметра дочерний компонент создаёт независимый файл кэша со своим таймером жизни.

Почему вложенный компонент выводится с ошибкой «компонент не найден»?

Обычно причина в неверном пути к шаблону (второй параметр IncludeComponent) или в том, что вложенный вызов происходит из контекста, где не подключён модуль main целиком - например, при вызове из cron-скрипта или внешнего API-эндпоинта без полной инициализации bitrix/modules/main/include/prolog_before.php. Проверьте, что вызов идёт из контекста с полным окружением Битрикс, а не из изолированного скрипта.

Как передать данные из родительского компонента в дочерний?

Только явно, через массив параметров при вызове IncludeComponent - автоматического наследования $arResult родителя нет. Соберите нужные значения в $arParams дочернего вызова: array(“PARENT_ID” => $arResult[“ID”], …), и уже внутри дочернего компонента используйте их как обычные входные параметры.

Есть задача?

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

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

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

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