Кастомизация чужого компонента изнутри - поиск и подмена
Разбираем кастомизацию с той стороны, откуда её видит платформа: где она ищет шаблон и сам компонент, в каком порядке выполняет файлы шаблона и почему часть правок исчезает после обновления продукта.
Механика
Вызов компонента называет сразу две вещи: имя самого компонента и имя его шаблона. Платформа разрешает их по двум независимым цепочкам каталогов, и обе цепочки заканчиваются каталогом ядра.
Компонент платформа ищет по паре «пространство имён и имя», перебирая всего два каталога. Каталог проекта просматривается раньше каталога платформы, и при совпадении путей выигрывает именно проектный.
Копия под пространством имён bitrix перехватывает все вызовы этого компонента
на сайте целиком. Копия под своим пространством имён работает только там, где имя
в вызове исправлено вручную.
Шаблон ищется иначе: платформа перебирает пять каталогов подряд и берёт первый
найденный. Пустое имя шаблона в вызове означает шаблон с именем .default, а не
отсутствие шаблона вообще.
«Текущий» шаблон сайта - это результат отдельного перебора, выполненного ещё до компонентов. Сайту назначают несколько шаблонов с условиями и сортировкой, и применяется первый подошедший.
Копия шаблона в шаблоне сайта отличается от правки штатного не содержимым, а судьбой при обновлении. Файлы внутри каталога платформы продукт перезаписывает молча, файлы каталога проекта не трогает.
Шаблон компонента - неделимое целое, и копируют его каталогом, а не отдельным файлом разметки. Недостающие файлы платформа не добирает из исходного шаблона, поэтому половинчатая копия ведёт себя непредсказуемо.
Файлы шаблона выполняются в жёстком порядке: правка результата, затем разметка, затем эпилог. Описание параметров и описание шаблона при работе компонента не подключаются вовсе.
Кэш результата режет эту цепочку пополам и меняет смысл каждой точки правки. Он хранит готовую разметку вместе с ограниченным набором ключей результата. При попадании в кэш ни правка результата, ни сам шаблон не выполняются, а эпилог отрабатывает всегда.
Сам компонент подменяют там, где параметров вызова и правки результата уже не хватает. Наследование обходится дешевле копии: наследник переопределяет один метод, остальное достаётся ему от родителя.
Шаги
- Определить, что именно меняется: разметка, состав данных или сама выборка компонента из базы.
- Узнать текущий шаблон сайта и убедиться, что копия ляжет в каталог компонентов именно этого шаблона.
- Скопировать каталог шаблона целиком в каталог проекта и указать его имя вторым аргументом вызова.
- Разнести правки по файлам: данные до разметки, разметку в шаблон, некэшируемое в эпилог.
- Проверить, что ни один изменённый файл не лежит внутри каталога платформы, и сбросить кэш компонента.
Код
Смотрим, какой шаблон выиграл поиск:
// временно в template.php того шаблона, который правимecho '<!-- шаблон сайта: ', SITE_TEMPLATE_ID, ' -->';echo '<!-- имя шаблона: ', $templateName, ' -->';echo '<!-- каталог шаблона: ', $templateFolder, ' -->';echo '<!-- компонент: ', $component->getName(), ' -->';$parent = $component->getParent(); // родитель у вложенного вызоваecho '<!-- родитель: ', ($parent ? $parent->getName() : 'нет'), ' -->';Пять строк комментария в исходном коде страницы отвечают на главный вопрос за минуту. Путь каталога сразу показывает, чей шаблон подключён: проекта, ядра или самого компонента.
Порядок перебора каталогов для шаблона vitrina:
1 /local/templates/<текущий>/components/bitrix/news.list/vitrina/2 /local/templates/.default/components/bitrix/news.list/vitrina/3 /bitrix/templates/<текущий>/components/bitrix/news.list/vitrina/4 /bitrix/templates/.default/components/bitrix/news.list/vitrina/5 /bitrix/components/bitrix/news.list/templates/vitrina/Перебор идёт сверху вниз и останавливается на первом найденном каталоге. Пустое
имя шаблона в вызове превращается в .default, поэтому одноимённая копия в общем
каталоге проекта молча перехватывает вывод.
Указываем имя своего шаблона в вызове:
$APPLICATION->IncludeComponent('bitrix:news.list', 'vitrina', [ 'IBLOCK_ID' => 5, 'CACHE_TIME' => 3600,]);// внутри шаблона другого компонента четвёртым аргументом передают родителя$APPLICATION->IncludeComponent('bitrix:news.list', 'vitrina', [], $component);// пустое имя шаблона означает .default, а не отсутствие шаблона$APPLICATION->IncludeComponent('bitrix:news.list', '', ['IBLOCK_ID' => 5]);Второй аргумент вызова и есть имя каталога копии. Четвёртый аргумент передаёт родительский компонент: без него шаблон вложенного компонента не ищется в составе родителя и его эпилог не подключается.
Копируем каталог шаблона целиком:
template.php - разметка, выполняется второйresult_modifier.php - правит результат до разметки и попадает в кэшcomponent_epilog.php - выполняется после разметки на каждом запросеstyle.css, script.js - платформа подключает сама по именам.parameters.php - только форма настроек, при работе не подключаетсяlang/ru/template.php - языковые фразы шаблонаДва файла с зарезервированными именами платформа подключает сама, остальное просят явно. Описание параметров нужно только форме настроек и при работе компонента не подключается.
Правим данные до разметки:
// result_modifier.php: $this здесь - объект шаблона, а не компонентаif (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();foreach ($arResult['ITEMS'] as $key => $item) { // готовим значения к выводу $arResult['ITEMS'][$key]['NAME_SHORT'] = mb_substr($item['NAME'], 0, 40);}$arResult['IDS'] = array_column($arResult['ITEMS'], 'ID');$this->__component->SetResultCacheKeys(['IDS']); // метод компонента// отложенные вызовы вроде SetTitle сюда не ставят: файл молчит при готовом кэшеМетоды компонента вызывают через свойство __component, потому что сам $this
принадлежит шаблону. Файл попадает в кэш вместе с разметкой и при готовом кэше не
выполняется вообще.
Выносим некэшируемое в эпилог:
// component_epilog.php: выполняется на каждом запросе, кэш его не закрывает\Bitrix\Main\Localization\Loc::loadLanguageFile(__FILE__); // автозагрузки нет$GLOBALS['APPLICATION']->SetTitle($arResult['NAME']);$GLOBALS['APPLICATION']->AddChainItem($arResult['NAME']); // хлебные крошки вне кэша// в $arResult здесь только ключи, объявленные через SetResultCacheKeys// фразы эпилога лежат в файле lang/ru/component_epilog.php рядом с нимЭпилог видит ровно те ключи результата, которые объявлены кэшируемыми. Код компонента после подключения шаблона выполняется позже эпилога и способен перекрыть уже установленный заголовок.
Подменяем сам компонент, а не шаблон:
/bitrix/components/bitrix/news.list/ - штатный, обновляется вместе с продуктом/local/components/bitrix/news.list/ - перехватывает все вызовы bitrix:news.list/local/components/vitrina/news.list/ - работает по имени vitrina:news.list/local/components/vitrina/news.list/templates/.default/ - его штатный шаблонКопия под пространством имён bitrix действует на весь сайт, включая
административные страницы и чужие решения. Копия под своим именем безопаснее, но
требует править имя в каждом вызове.
Расширяем чужой компонент наследником:
CBitrixComponent::includeComponentClass('bitrix:catalog.section');
class VitrinaCatalogSection extends CatalogSectionComponent{ public function onPrepareComponentParams($arParams): array { $arParams = parent::onPrepareComponentParams($arParams); // база от родителя $arParams['ELEMENT_SORT_FIELD'] = 'PROPERTY_PRIORITY'; // своя сортировка return $arParams; }}Наследник получает всю логику родителя и меняет только нужный метод. Копии исходного кода не остаётся, поэтому исправления платформы продолжают приезжать вместе с обновлениями продукта.
Ограничения
Копия шаблона не обновляется вместе с продуктом и постепенно расходится с исходником. Новые возможности штатного шаблона в неё не приезжают, и раз в год копию сверяют вручную.
Копия компонента под пространством имён bitrix действует на весь сайт разом.
Ошибка в такой копии роняет не одну страницу, а каждую, где компонент
вызывается.
Частичная копия компонента не работает: файлы компонента используются платформой как единое целое. Вызов из скопированного файла способен уйти в исходный каталог и выполнить чужой код.
Шаблон не меняет ни порядок вывода, ни саму выборку компонента из базы. Сортировку, фильтр и постраничную навигацию правят параметрами вызова, наследником или своим компонентом.
Одно имя .default на два места не годится у комплексных компонентов. Шаблон
простого компонента, работающего и отдельно, и внутри комплексного, обязан
получить отличное от .default имя.
Типичные проблемы
Копия шаблона лежит в каталоге проекта, а сайт выводит штатную вёрстку.
Вызов компонента по-прежнему называет прежнее имя шаблона, и поиск заканчивается в каталоге платформы. Имя каталога копии и есть имя шаблона: его подставляют вторым аргументом вызова.
Правки видны на одних страницах и не видны на других.
Сайту назначено несколько шаблонов с условиями, а копия положена в каталог только одного. Платформа применяет первый шаблон, чьё условие выполнено, и ищет компоненты уже внутри него.
Компонент скопирован в своё пространство имён, а страница не изменилась.
Имя в вызове осталось прежним, и платформа продолжает брать штатный компонент из каталога ядра. Своё пространство имён требует ручной правки имени во всех вызовах на сайте.
Скопированный компонент подключает файл из исходного каталога платформы.
Скопирована часть файлов, а компонент используется платформой как неделимое целое. Каталог компонента копируют полностью, вместе с языковыми файлами и вложенными скриптами.
Заголовок страницы обновляется только после сброса кэша компонента.
Установка заголовка стоит в правке результата, а тот при готовом кэше не выполняется. Отложенные вызовы вроде заголовка и метатегов переносят в эпилог шаблона.
После обновления продукта правки в шаблоне пропали без следа.
Менялся файл внутри каталога платформы, и обновление вернуло его в исходный вид. Правки держат в каталоге проекта, а каталог платформы считают доступным только на чтение.
Частые вопросы
Почему компонент подтягивает шаблон из папки bitrix?
Поиск дошёл до каталога платформы, не найдя копию раньше. Проверяют имя шаблона в вызове и каталог того шаблона сайта, который сейчас применён.
Как правильно скопировать шаблон компонента?
Каталогом целиком, в каталог компонентов своего шаблона сайта, под понятным именем. Отдельный файл разметки без соседей работает не так, как исходный шаблон.
Перенёс компонент в local, и ничего не изменилось. Почему?
Скорее всего сменилось пространство имён, а вызовы остались прежними. Либо каталог скопирован не полностью, и часть файлов по-прежнему берётся из платформы.
Где ставить заголовок страницы: в правке результата или в эпилоге?
Только в эпилоге: правка результата кэшируется и при готовом кэше не выполняется. Компонент, вызванный ниже по странице, всё равно способен перекрыть заголовок.
Обязательно ли копировать компонент целиком?
Нет, чаще хватает копии шаблона или наследника от класса чужого компонента. Полную копию делают, когда меняется сама выборка, а параметров вызова не хватает.
Смежное
- Кастомизация компонента - оглавление подтемы
- Компоненты 2.0 - устройство компонентов целиком
- Шаблон чужого компонента: копия, доработка результата, эпилог - пошаговый рецепт той же работы
- Компонент изнутри: вызов, кэш, шаблон, эпилог - жизненный цикл своего компонента
- Наследование чужого компонента: расширить, не копируя - подмена логики без копии кода
- Правки шаблона компонента не видны: разбор причин - разбор конкретного случая с копией
- Компонент не отдаёт нужных данных: причины по убыванию частоты - когда правка результата не спасает
- Шаблон сайта не применяется: разбор причин - какой шаблон сайта считается текущим
- Шаблоны сайта: header, footer, свойства страниц - каталог, где живут копии
- Комплексный компонент изнутри: маршруты, переменные, выбор страницы - шаблоны внутри комплексного
- Свой компонент: структура, параметры, кэш результата - когда копии уже мало
- Кастомизация Vue-компонента: мутация, клон, порядок загрузки - то же для клиентских компонентов ядра