Путь запроса на витрине - пролог, компоненты, буфер, эпилог
Разбираем, как собирается страница витрины: пролог, компоненты и их кэш, отложенные функции, буфер вывода и эпилог.
Механика
Страница начинается с пролога и заканчивается эпилогом. Пролог подключает ядро, поднимает пользователя, отдаёт шапку сайта, эпилог печатает подвал и завершает ответ.
Между прологом и эпилогом выполняется содержимое самой страницы. Компоненты вызываются сверху вниз в том порядке, в каком они написаны, и каждый успевает повлиять на страницу до следующего.
Весь вывод страницы копится в буфере, а не уходит браузеру сразу же. Поэтому платформа умеет править уже выведенную шапку и подставлять в неё то, что стало известно позже.
Этот приём платформы и называется отложенными функциями страницы. Заголовок страницы, метатеги и подключённые файлы стилей записываются в шапку задним числом, когда содержимое уже отработало.
Кэш компонента меняет всю эту картину сильнее любого другого механизма. Пока кэш действителен, код внутри компонента не выполняется вовсе, и всё, что он делал, из сборки страницы просто исчезает.
Отсюда и появилось правило про файл правки результата и эпилог компонента. Файл правки результата работает только при сборке кэша, а эпилог компонента выполняется на каждом запросе - в нём и живёт всё, что должно работать всегда.
Композитная страница обрывает эту цепочку в самом её начале. Готовая копия отдаётся веб-сервером почти сразу, а личные блоки догружаются вторым запросом уже к самой платформе.
Порядок сборки объясняет заодно и стоимость самой страницы в запросах. Каждый компонент - это выборки, кэш и разметка, и лишний вызов в шапке дорожает ровно столько же, сколько такой же вызов в теле страницы.
Понимание этого порядка отвечает сразу на половину вопросов о витрине. «Почему заголовок не меняется», «почему стили не подключились», «почему счётчик показывает чужое число» - это всё вопросы о месте кода в цепочке.
Шаги
- Определить, где именно должен выполняться ваш код: до, внутри или после компонента.
- Проверить, не попадает ли он внутрь кэша компонента и не исчезает ли вместе с ним.
- Для правок шапки страницы использовать отложенные функции, а не прямой вывод.
- Личные блоки помечать так, чтобы они пережили сборку композитной копии страницы.
- Проверить результат на второй загрузке страницы, когда кэш компонента уже собран.
Код
Смотрим, что уже случилось к моменту кода:
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>[^<]*'# метки динамических областей показывают, что страница композитнаяОграничения
Порядок вызова компонентов менять нельзя. Компонент, которому нужны данные другого, ставят ниже по странице, а не рассчитывают на волшебство платформы.
Отложенные функции перестают работать после того, как ответ ушёл браузеру. Всё, что должно попасть в шапку, объявляют до эпилога: позже буфер уже закрыт и подстановка не сработает.
Внутри кэша компонента нельзя рассчитывать на данные текущего пользователя сайта. Кэш общий для всех, и личное там появляться не должно ни при каких настройках.
Пролог административной части устроен иначе и живёт по своим правилам. Там свои файлы подключения и свой порядок, и приёмы витрины туда не переносятся без правок.
Композитная копия страницы не знает о вашем коде вовсе. Любая правка, которая должна быть видна сразу, обязана жить в динамической области или в отложенной функции.
Типичные проблемы
Заголовок страницы не меняется из кода страницы.
Заголовок печатается напрямую, а шапка уже выведена прологом раньше. Заголовок и метатеги правят отложенными функциями, значения которых подставляются перед самим ответом.
Код в правке результата перестал выполняться.
Кэш компонента уже собран, и файл правки результата больше не вызывается вовсе. Код, который обязан работать всегда, переносят в эпилог шаблона этого компонента.
Стили подключились, но не попали в шапку.
Подключение вызвано после закрытия буфера страницы, когда подстановка в шапку уже невозможна. Ресурсы подключают до эпилога страницы, а лучше в шаблоне или в компоненте.
Личный блок показывает данные другого посетителя.
Личный блок попал в общий кэш компонента или в композитную копию страницы. Личное выносят в отложенную функцию либо в динамическую область.
Правка видна только после сброса кэша.
Изменение сделано внутри компонента, результат которого отдаётся посетителю из готового кэша. Либо сбрасывают кэш при изменении данных, либо переносят правку в эпилог компонента.
Счётчик посещений считает вдвое меньше.
Код счётчика попал в статическую часть композитной страницы и не выполняется. Счётчики и код аналитики переносят в динамические области страницы заранее.
Частые вопросы
Почему заголовок нужно ставить особым способом?
Шапка страницы выводится раньше вашего кода, в прологе. Отложенная функция оставляет в ней метку и подставляет значение перед самой отправкой ответа.
Чем правка результата отличается от эпилога?
Правка результата выполняется только при сборке кэша компонента, эпилог - на каждом запросе. Всё, что зависит от пользователя или должно работать всегда, живёт в эпилоге.
Где подключать свои стили и скрипты?
В шаблоне сайта или в шаблоне компонента, до закрытия буфера страницы. Подключение из кода после эпилога в шапку уже не попадёт.
Как понять, отдаётся ли страница из композита?
По служебному заголовку ответа и по меткам динамических областей в разметке. Оба признака видны обычным запросом с консоли.
Можно ли выполнить свой код до пролога?
Да, в файле инициализации: он подключается раньше всего остального. Там же вешают обработчики событий, которые должны сработать на любом запросе.
Смежное
- Шаблон сайта - оглавление подтемы
- Стили и скрипты в шаблоне: подключение, порядок, кэш - куда попадают ресурсы страницы
- Шаблон чужого компонента: копия, доработка результата, эпилог - правка результата и эпилог на практике
- Кэш не срабатывает: страница собирается заново каждый раз - когда кэша нет вовсе
- Композитный сайт: включение, динамические области, сброс - что меняет композит в этой цепочке
- Заголовок и метатеги не те: разбор причин - разбор заголовка страницы
- Файл инициализации: порядок подключения, что доступно, ошибки - код, который выполняется раньше пролога
- Шаблоны сайта и компонентов - устройство шаблонов целиком