Перейти к содержимому

Путь запроса на витрине - пролог, компоненты, буфер, эпилог

Разбираем, как собирается страница витрины: пролог, компоненты и их кэш, отложенные функции, буфер вывода и эпилог.

Механика

Страница начинается с пролога и заканчивается эпилогом. Пролог подключает ядро, поднимает пользователя, отдаёт шапку сайта, эпилог печатает подвал и завершает ответ.

Между прологом и эпилогом выполняется содержимое самой страницы. Компоненты вызываются сверху вниз в том порядке, в каком они написаны, и каждый успевает повлиять на страницу до следующего.

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

Этот приём платформы и называется отложенными функциями страницы. Заголовок страницы, метатеги и подключённые файлы стилей записываются в шапку задним числом, когда содержимое уже отработало.

Кэш компонента меняет всю эту картину сильнее любого другого механизма. Пока кэш действителен, код внутри компонента не выполняется вовсе, и всё, что он делал, из сборки страницы просто исчезает.

Отсюда и появилось правило про файл правки результата и эпилог компонента. Файл правки результата работает только при сборке кэша, а эпилог компонента выполняется на каждом запросе - в нём и живёт всё, что должно работать всегда.

Композитная страница обрывает эту цепочку в самом её начале. Готовая копия отдаётся веб-сервером почти сразу, а личные блоки догружаются вторым запросом уже к самой платформе.

Порядок сборки объясняет заодно и стоимость самой страницы в запросах. Каждый компонент - это выборки, кэш и разметка, и лишний вызов в шапке дорожает ровно столько же, сколько такой же вызов в теле страницы.

Понимание этого порядка отвечает сразу на половину вопросов о витрине. «Почему заголовок не меняется», «почему стили не подключились», «почему счётчик показывает чужое число» - это всё вопросы о месте кода в цепочке.

Шаги

  1. Определить, где именно должен выполняться ваш код: до, внутри или после компонента.
  2. Проверить, не попадает ли он внутрь кэша компонента и не исчезает ли вместе с ним.
  3. Для правок шапки страницы использовать отложенные функции, а не прямой вывод.
  4. Личные блоки помечать так, чтобы они пережили сборку композитной копии страницы.
  5. Проверить результат на второй загрузке страницы, когда кэш компонента уже собран.

Код

Смотрим, что уже случилось к моменту кода:

printf("шаблон: %s, страница: %s\n", SITE_TEMPLATE_ID, $APPLICATION->GetCurPage());
printf("пользователь: %d, композит: %s\n", $USER->GetID(),
defined('BX_COMPOSITE_MODE') ? 'да' : 'нет');
printf("режим правки: %s\n", $APPLICATION->GetShowIncludeAreas() ? 'да' : 'нет');
printf("буфер открыт: %d уровней\n", ob_get_level());
// код страницы выполняется после пролога: ядро и пользователь уже доступны

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

Меняем заголовок задним числом:

$APPLICATION->SetTitle('Название из данных страницы');
$APPLICATION->SetPageProperty('description', $element['PREVIEW_TEXT']);
$APPLICATION->SetAdditionalCSS('/local/templates/main/css/detail.css');
// все три строки правят уже выведенную шапку через отложенные функции
// то же самое из шаблона компонента работает только при сборке кэша

Отложенные функции работают благодаря буферу. Платформа оставляет в шапке метку, а перед отправкой ответа подставляет туда актуальное значение.

Кладём личный блок в отложенную функцию:

$APPLICATION->AddBufferContent(static function () {
global $USER;
return $USER->IsAuthorized() ? 'Здравствуйте, ' . $USER->GetFullName() : 'Войти';
});
// функция выполняется после сборки всей страницы, перед отправкой ответа
// её результат не попадает ни в кэш компонента, ни в композитную копию

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

Проверяем, выполняется ли код внутри кэша:

// result_modifier.php шаблона компонента: выполняется при сборке кэша
\Bitrix\Main\Diag\Debug::writeToFile(date('H:i:s'), 'result_modifier', 'component.log');
// component_epilog.php того же шаблона: выполняется на каждом запросе
\Bitrix\Main\Diag\Debug::writeToFile(date('H:i:s'), 'epilog', 'component.log');
// сравниваем две загрузки подряд: во второй строка правки результата не появится

Две строки в журнале на первой загрузке и одна на второй - это и есть разница между правкой результата и эпилогом. Первый файл работает только при сборке кэша, второй выполняется всегда.

Ещё одна важная деталь этого порядка касается перенаправлений посетителя. Отправить посетителя на другой адрес можно только до того, как в буфер ушла хотя бы одна строка ответа, иначе заголовки уже отправлены.

Отключаем кэш на время разбора:

$APPLICATION->IncludeComponent('bitrix:catalog.section', '', [
'CACHE_TYPE' => 'N', // только на время разбора, не на бою
'CACHE_TIME' => 0,
'IBLOCK_ID' => $iblockId,
]);
// выключенный кэш показывает, что именно делает компонент на каждом запросе

Смотрим состав ответа глазами браузера:

Окно терминала
curl -sI https://example.com/catalog/ | grep -iE 'x-bitrix|cache-control'
curl -s https://example.com/catalog/ | grep -c 'bxdynamic'
curl -s https://example.com/catalog/ | grep -o '<title>[^<]*'
# метки динамических областей показывают, что страница композитная

Ограничения

Порядок вызова компонентов менять нельзя. Компонент, которому нужны данные другого, ставят ниже по странице, а не рассчитывают на волшебство платформы.

Отложенные функции перестают работать после того, как ответ ушёл браузеру. Всё, что должно попасть в шапку, объявляют до эпилога: позже буфер уже закрыт и подстановка не сработает.

Внутри кэша компонента нельзя рассчитывать на данные текущего пользователя сайта. Кэш общий для всех, и личное там появляться не должно ни при каких настройках.

Пролог административной части устроен иначе и живёт по своим правилам. Там свои файлы подключения и свой порядок, и приёмы витрины туда не переносятся без правок.

Композитная копия страницы не знает о вашем коде вовсе. Любая правка, которая должна быть видна сразу, обязана жить в динамической области или в отложенной функции.

Типичные проблемы

Заголовок страницы не меняется из кода страницы.

Заголовок печатается напрямую, а шапка уже выведена прологом раньше. Заголовок и метатеги правят отложенными функциями, значения которых подставляются перед самим ответом.

Код в правке результата перестал выполняться.

Кэш компонента уже собран, и файл правки результата больше не вызывается вовсе. Код, который обязан работать всегда, переносят в эпилог шаблона этого компонента.

Стили подключились, но не попали в шапку.

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

Личный блок показывает данные другого посетителя.

Личный блок попал в общий кэш компонента или в композитную копию страницы. Личное выносят в отложенную функцию либо в динамическую область.

Правка видна только после сброса кэша.

Изменение сделано внутри компонента, результат которого отдаётся посетителю из готового кэша. Либо сбрасывают кэш при изменении данных, либо переносят правку в эпилог компонента.

Счётчик посещений считает вдвое меньше.

Код счётчика попал в статическую часть композитной страницы и не выполняется. Счётчики и код аналитики переносят в динамические области страницы заранее.

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

Почему заголовок нужно ставить особым способом?

Шапка страницы выводится раньше вашего кода, в прологе. Отложенная функция оставляет в ней метку и подставляет значение перед самой отправкой ответа.

Чем правка результата отличается от эпилога?

Правка результата выполняется только при сборке кэша компонента, эпилог - на каждом запросе. Всё, что зависит от пользователя или должно работать всегда, живёт в эпилоге.

Где подключать свои стили и скрипты?

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

Как понять, отдаётся ли страница из композита?

По служебному заголовку ответа и по меткам динамических областей в разметке. Оба признака видны обычным запросом с консоли.

Можно ли выполнить свой код до пролога?

Да, в файле инициализации: он подключается раньше всего остального. Там же вешают обработчики событий, которые должны сработать на любом запросе.

Смежное

Первоисточник