Шаблон сайта выводит H1 раньше, чем компонент получает название товара. Хлебные крошки находятся над содержимым, а последний пункт цепочки становится известен внутри компонента. В обоих случаях место вывода уже пройдено, но данные ещё можно подставить — через отложенные функции Битрикс.
Механизм оставляет место в буфере страницы и вычисляет его содержимое при сборке HTML. Для заголовков и метатегов есть штатные методы, для готового HTML — именованные области, для собственного вычисления — AddBufferContent(). Кеш компонента влияет на то, какой код выполнится повторно, поэтому для шаблона и эпилога приёмы различаются.
Вывести поздний заголовок: ShowTitle() вместо GetTitle()
Если H1 расположен в header.php шаблона сайта, обычное получение заголовка может сработать слишком рано:
<h1><?= $APPLICATION->GetTitle() ?></h1>
GetTitle() возвращает текущее значение. Последующий SetTitle() не изменит уже выведенную строку. Замените этот фрагмент отложенным выводом:
<h1><?php $APPLICATION->ShowTitle(false); ?></h1>
В коде страницы после подключения /bitrix/header.php задайте заголовок:
$APPLICATION->SetTitle('Доставка');
Когда Битрикс соберёт буфер, H1 получит «Доставка». ShowTitle() регистрирует получение значения через AddBufferContent(): функция читает заголовок позднее, а её результат вставляется на место вызова в шаблоне.
Это работа в пределах текущего серверного запроса. Отложенная функция не создаёт фоновую задачу и не запускает JavaScript. Сам ShowTitle() не нужно оборачивать в echo или ещё один AddBufferContent().
Задать разные H1 и title, вывести description
Название в тексте страницы и заголовок вкладки браузера часто различаются. Например, H1 должен быть коротким, а <title> — уточнять тему. В коде страницы после подключения шапки задайте оба значения и описание:
$APPLICATION->SetTitle('Доставка');
$APPLICATION->SetPageProperty('title', 'Условия доставки заказов');
$APPLICATION->SetPageProperty('description', 'Сроки и способы получения заказа.');
Внутри <head> файла header.php шаблона сайта:
<title><?php $APPLICATION->ShowTitle(); ?></title>
<?php $APPLICATION->ShowHead(); ?>
Для H1 внутри <body>:
<h1><?php $APPLICATION->ShowTitle(false); ?></h1>
ShowTitle() без параметра сначала ищет свойство title, затем использует значение из SetTitle(). Параметр false отключает поиск свойства: H1 получит «Доставка», а <title> — «Условия доставки заказов».
ShowHead() уже выводит стандартные метатеги и ресурсы страницы. Не добавляйте рядом отдельный ShowMeta('description'), иначе описание может продублироваться. ShowMeta() нужен при самостоятельной сборке метатегов, а ShowProperty() выводит значение свойства без разметки метатега. Он не экранирует произвольный HTML автоматически.
Для ресурсов также существуют ShowHeadStrings(), ShowHeadScripts() и ShowCSS(); обычно достаточно штатного ShowHead(). ShowTitle() удаляет теги, но не является универсальным экранированием для любого HTML-контекста. Пользовательский текст внутри собственного HTML-фрагмента нужно экранировать при формировании этого фрагмента.
Добавить пункт хлебных крошек ниже места их вывода
Хлебные крошки обычно размещают в начале рабочей области шаблона, а данные текущего объекта получает компонент ниже. Стандартный компонент bitrix:breadcrumb использует отложенное получение цепочки, поэтому позднее добавленный пункт попадёт на своё место.
В header.php шаблона, внутри <body>, подключите цепочку:
$APPLICATION->IncludeComponent('bitrix:breadcrumb', '', [
'START_FROM' => '0',
'PATH' => '',
'SITE_ID' => SITE_ID,
]);
В коде страницы после подключения шапки добавьте пункт:
$APPLICATION->AddChainItem('Доставка');
Компонент регистрирует вызов GetNavChain() через AddBufferContent(). Цепочка собирается после того, как страница добавила свои пункты. Если компонент контента уже добавляет этот пункт сам, повторный AddChainItem() не нужен: отложенный вывод не удаляет дубли.
Для цепочки есть и штатный ShowNavChain(). Другой пример встроенного отложенного вывода — ShowPanel(), который размещает административную панель на публичной странице. Писать собственный callback для этих задач обычно не требуется.
Вывести готовый HTML выше по странице: ShowViewContent() и AddViewContent()
Именованная область подходит для сообщения, баннера или дополнительного блока, который должен появиться раньше кода, формирующего его содержимое. В шаблоне сайта обозначьте место:
$APPLICATION->ShowViewContent('catalog_notice');
Позднее, например в коде страницы после подключения шапки, добавьте HTML:
$APPLICATION->AddViewContent(
'catalog_notice',
'<p>Наличие товара уточняется при подтверждении заказа.</p>',
100
);
ShowViewContent() откладывает чтение области, а AddViewContent() добавляет готовую строку. Третий аргумент задаёт позицию: фрагмент с позицией 100 появится раньше фрагмента с позицией 200, независимо от порядка добавления.
Несколько добавлений объединяются. Два вызова ShowViewContent() покажут область в двух местах, а пустая область ничего не выведет. При необходимости очистить её используйте $APPLICATION->clearViewContent('catalog_notice'). Имя должно отличаться от имён областей других компонентов.
Так удобно добавлять HTML из кода страницы или эпилога компонента. Для кешируемого template.php нужен следующий приём: обычный AddViewContent() не сохраняет область в данных кеша шаблона.
Вывести анонс news.detail над компонентом и сохранить его в кеше
Допустим, bitrix:news.detail выводит подробный текст новости, а её анонс должен находиться выше — в шаблоне сайта. Если добавить область обычным AddViewContent() из template.php, она может исчезнуть при попадании в кеш. Пара SetViewTarget() / EndViewTarget() сохраняет захваченный HTML вместе с данными компонента.
В header.php шаблона сайта, внутри <body> перед содержимым страницы, задайте место анонса:
$APPLICATION->ShowViewContent('news_announcement');
В template.php компонента news.detail:
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) { die(); }
/** @var CBitrixComponentTemplate $this */
$announcement = (string)($arResult['~PREVIEW_TEXT'] ?? '');
?>
<?php if ($announcement !== ''): ?>
<?php $this->SetViewTarget('news_announcement', 100); ?>
<aside class="news-announcement">
<?= htmlspecialchars(strip_tags($announcement), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>
</aside>
<?php $this->EndViewTarget(); ?>
<?php endif; ?>
news.detail уже получает анонс элемента. ~PREVIEW_TEXT содержит исходное значение до HTML-экранирования; пример удаляет разметку и выводит анонс обычным текстом. Если шаблон уже показывает этот анонс рядом с подробным текстом, уберите прежний вывод, чтобы не получить повтор.
Здесь $this — объект шаблона компонента. SetViewTarget() начинает захват, а EndViewTarget() передаёт HTML в область и регистрирует его у компонента. При попадании в кеш Битрикс восстановит область без повторного выполнения template.php. SetResultCacheKeys() для анонса не нужен: сохраняется уже сформированный HTML. Если анонс не заполнен, пример не создаёт пустой <aside>.
Закрывайте захват явно. Начало другой области завершает предыдущую, поэтому эти вызовы не стоит использовать как вложенные контейнеры. Внутри захвата формируйте обычный HTML, без новых отложенных функций и ручного завершения буферов.
Передать данные из шаблона news.detail в component_epilog.php
Эпилог нужен для действий, которые должны повторяться и при попадании в кеш компонента. Например, над новостью нужно вывести подпись «Материал: название новости» через отдельное свойство страницы. Подготовим значение в result_modifier.php шаблона bitrix:news.detail, сохраним его в результате и установим свойство в component_epilog.php.
В result_modifier.php рядом с template.php добавьте:
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) { die(); }
/** @var CBitrixComponentTemplate $this */
$name = trim((string)($arResult['~NAME'] ?? ''));
$arResult['ARTICLE_LABEL'] = $name !== '' ? 'Материал: ' . $name : '';
$this->getComponent()->SetResultCacheKeys(['ARTICLE_LABEL']);
result_modifier.php получает результат штатного компонента до вывода шаблона. Через getComponent() он обращается к компоненту и добавляет ARTICLE_LABEL к сохраняемым ключам. Переписывать component.php или добавлять свой StartResultCache() не нужно.
В component_epilog.php:
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) { die(); }
$APPLICATION->SetPageProperty(
'article_label',
htmlspecialchars((string)($arResult['ARTICLE_LABEL'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
);
В header.php шаблона сайта, внутри <body>, разместите вывод свойства:
$APPLICATION->ShowProperty('article_label');
ShowProperty() прочитает значение при сборке страницы. Он сам не экранирует HTML, поэтому эпилог сохраняет в свойство уже экранированный текст. Например, для новости «Открытие магазина» на месте вызова появится «Материал: Открытие магазина».
При первом построении кеша выполняется result_modifier.php, а ARTICLE_LABEL сохраняется в $arResult. На повторном запросе Битрикс берёт сохранённое значение и снова вызывает эпилог. После добавления нового ключа очистите кеш компонента: прежняя запись его не содержит.
Для стандартных H1 и метатегов сначала используйте параметры самого news.detail: компонент умеет устанавливать их и после получения результата из кеша. Этот пример использует отдельное свойство article_label, чтобы не конкурировать со штатной установкой заголовков.
component_epilog.php — эпилог конкретного шаблона компонента, а не событие OnEpilog всей страницы. Здесь доступны $arResult, $arParams и $component, но $this уже не объект CBitrixComponentTemplate: переносить сюда $this->SetViewTarget() нельзя. Если из эпилога нужен готовый HTML в области, используйте $APPLICATION->AddViewContent().
Вычислить свой HTML при сборке страницы: AddBufferContent()
Именованная область хранит готовые строки. Собственный callback нужен, если значение надо прочитать именно при сборке буфера. Например, шаблон заранее оставляет место для плашки, а текст плашки задаётся позднее через свойство страницы.
В /local/php_interface/init.php определите callback:
<?php
function projectPageBadge(string $property): string
{
$text = (string)$GLOBALS['APPLICATION']->GetPageProperty($property, '');
if ($text === '') { return ''; }
return '<span class="page-badge">'
. htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. '</span>';
}
В header.php шаблона, внутри <body>, зарегистрируйте вывод:
$APPLICATION->AddBufferContent('projectPageBadge', 'page_badge');
В коде страницы после подключения шапки задайте значение:
$APPLICATION->SetPageProperty('page_badge', 'Информация для покупателя');
На месте регистрации появится <span> с заданным текстом. Callback получает имя свойства и читает его значение позднее. Он должен вернуть строку: echo внутри функции не заменяет return.
Не передавайте позднее значение уже вычисленным аргументом:
// Этот вызов запомнит текущее значение свойства.
$APPLICATION->AddBufferContent(
'htmlspecialchars',
(string)$APPLICATION->GetPageProperty('page_badge', ''),
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
PHP вычисляет аргументы при регистрации. Если свойство ещё пустое, callback получит пустую строку даже после её изменения. Передача ключа page_badge позволяет прочитать актуальные данные внутри функции.
Именованная функция также позволяет избежать документированного ограничения анонимных callbacks при AJAX_MODE = Y у компонентов. Сам режим AJAX всё равно требует отдельной проверки. На этапе сборки HTML лучше читать уже подготовленные данные, а не выполнять сетевые запросы или повторные выборки из базы.
Выбрать событие по этапу: OnEpilog и завершение буфера
События нужны, когда действие относится к завершению страницы целиком, а не к одному шаблону компонента. В обычном завершении публичной страницы OnEpilog вызывается до сборки основного буфера: в нём можно установить свойства, которые отложенные функции ещё не прочитали. Готовый HTML это событие не получает.
Порядок внутри завершения буфера:
OnBeforeEndBufferContent— перед вычислением отложенных участков; без HTML-параметра.- Вызовы зарегистрированных отложенных функций.
- Объединение их результатов с остальными частями страницы.
OnEndBufferContent— обработчики получают собранную строку HTML по ссылке.
Если задача — задать заголовок, используйте свойства страницы. Если нужно изменить уже собранную разметку, подходит OnEndBufferContent. Ядро может продолжить обработку ресурсов и композита после этого события, поэтому оно не означает окончание любой обработки ответа.
Заменить фрагмент готового HTML: OnEndBufferContent
Обработку буфера используйте, когда нет подходящей точки для изменения исходного вывода. Например, собственный шаблон оставляет маркер, который нужно заменить при сборке страницы. В /local/php_interface/init.php добавьте обработчик:
<?php
AddEventHandler('main', 'OnEndBufferContent', 'projectReplacePageMarker');
function projectReplacePageMarker(&$html): void
{
global $APPLICATION;
if ($APPLICATION->GetCurPage() !== '/delivery/index.php'
|| ($_SERVER['REQUEST_METHOD'] ?? '') !== 'GET'
|| stripos($html, '</html>') === false) {
return;
}
$html = str_replace('<!--PAGE_NOTICE-->',
'<p>Наличие товара уточняется при подтверждении заказа.</p>', $html);
}
В /delivery/index.php, между подключениями /bitrix/header.php и /bitrix/footer.php, выведите маркер:
echo '<!--PAGE_NOTICE-->';
На его месте появится абзац. Условие ограничивает обработчик выбранной страницей и полным HTML-ответом на GET-запрос. Если путь другой, замените его в проверке. При отсутствии маркера строка не изменится.
Композитная обработка может вызвать OnEndBufferContent повторно для другой версии буфера. Замена маркера переносит повторный вызов безопасно: она не дописывает второй абзац к уже обработанной строке. Не используйте такое событие для отправки уведомлений или увеличения счётчиков, которые должны срабатывать ровно один раз.
Найти причину ошибки: позднее значение, кеш или сброшенный буфер
Если заголовок остаётся старым, проверьте ранний GetTitle() и последующие вызовы SetTitle(). Для различающихся H1 и <title> проверьте параметр ShowTitle().
Если область появляется только после очистки кеша, найдите AddViewContent() в кешируемом шаблоне. Для захвата HTML используйте SetViewTarget(), а установку свойств страницы перенесите в component_epilog.php. Когда в эпилоге отсутствует поле, проверьте SetResultCacheKeys() и очистите старую запись кеша.
Если блок повторяется, проверьте количество вызовов ShowViewContent() и добавлений в область. AddViewContent() дополняет содержимое, а не заменяет предыдущую строку.
Если callback выполняется сразу, проверьте жизненный цикл страницы. Без BX_BUFFER_USED === true AddBufferContent() немедленно вызывает функцию и выводит результат. Не определяйте эту константу вручную: нужны штатные пролог, эпилог и управление буфером.
Если обычная страница работает, а AJAX-ответ — нет, проверьте наличие полного цикла страницы и вызовы RestartBuffer(). Этот метод сбрасывает накопленные части буфера и регистрации отложенного вывода; он не запускает их повторно. Лишние ob_end_clean(), ob_end_flush() и незакрытый SetViewTarget() также нарушают ожидаемую последовательность.
Кеш компонента и композит — разные уровни. SetViewTarget() сохраняет HTML области вместе с компонентом, но не делает её персональной. Имя пользователя, корзина и персональная цена требуют соответствующего разделения кеша, а при композите — динамических областей. Сам AddBufferContent() не гарантирует выполнение PHP при выдаче сохранённой композитной страницы.
После подключения выбранного приёма сравните исходный HTML первого ответа после очистки кеша и повторного ответа без очистки. Нужный заголовок или блок должен присутствовать в обоих. Для своего HTML проверьте текст с кавычками, <, > и &: символы должны оставаться текстом. AJAX и композит проверяйте отдельно, если проект их использует.