Наследование шаблонов в ванильном PHP за 35 строк кода?

от автора

Попал мне как-то под руку проект на WordPress (WP), где понадобилось сделать кастомную тему. В WP шаблоны нативные, что хорошо, — не надо учить дополнительный язык. Но очень захотелось понаследовать шаблоны как в Twig, а PHP из коробки так не умеет.

Осталось решить вопрос наследования. После изучения проблемы, вдохновляться было решено библиотекой phpti, в которой нашлось пару моментов, которые очень захотелось поправить:

  • Автор библиотеки большими буквами написал “Every Block is Always Executed!“, т. е. все блоки выполняются, даже если переопределены, и никогда не будут выведены.

  • Второй момент — библиотека под капотом использует анализ стека вызовов, что, как мне кажется, чуть-чуть читерство.

  • Третий момент — хочу ещё быстрее. В библиотеке активно используется ob_start на чём и попробуем сэкономить пару спичек.

Библиотека phpti построена вокруг основной конструкции startblock/endblock и наследования при помощи import в начале файла:

// layout.php <!-- разметка --> <?php startblock('blockName') ?> <?php endblock() ?> <!-- разметка -->
// index.php <?php include 'layout.php' ?> <!-- указываем родительский шаблон --> <?php startblock('blockName') ?>     <!-- контент блока --> <?php endblock() ?>

Некоторые наблюдения:

  • Вызовы вроде start/end можно заменить на анонимную функцию. Это избавит от необходимости сопоставлять вложенные старты и энды, а также сделает код контента ленивым.

  • Родительский шаблон указывается сверху файла. Это значит, что придётся сначала отрабатывать родительский, а потом дочерний шаблон. А значит придётся много чего буферизировать. А почему бы родителя не указать в конце?

  • Можно заметить, что на самом деле есть два разных типа блоков: одни определяются внутри шаблонов куда и выводят результат по месту, а другие по месту определения ничего не выводят, а только переопределяют родительские блоки.

С учётом вышесказанного, переделаем конструкцию на следующую:

// layout.php <!-- разметка --> <?php slot('blockName', function(){ ?> <?php }) ?> <!-- разметка -->
// index.php <?php block('blockName', function(){ ?>     <!-- контент блока --> <?php }) ?> <?php include 'layout.php' ?> <!-- указываем родительский шаблон -->

Разделив блоки на slot и block, библиотеке больше не нужно пытаться понять, когда нужно выводить результат, а когда нужно только переопределить блок.

Вроде выглядит не слишком монструозно по сравнению с оригинальной структурой. Посмотрим, покроет ли такой подход все хотелки.

root.php — базовый шаблон, внизу иерархии:

<!DOCTYPE html> <html>   <head>     <title><?php slot('title') /* слот - место в шаблоне */ ?></title>   </head>   <body>     <div id="root">       <?php slot('body', function () { /* слот с контентом по дефолту */?>          <p>'body' :: root.php</p>        <?php }) ?>     </div>   </body> </html>

two-columns.php — промежуточный шаблон:

<?php  block('title', function () { /* блок - контент для вставки в слот */?>   Title :: two-columns.php <?php }); block('body', function () { ?>   <div id="two-columnts">     <div id="main">       <?php slot('main', function () { /* слот внутри блока */?>     <p>'main' :: two-columns.php</p>   <?php }) ?> </div> <div id="side">   <?php slot('side', function () { ?> <p>'side' :: two-columns.php</p>    <?php }) ?> </div>   </div>   <div id="footer">     <?php slot('footer', function () { ?>   <p>'footer' :: two-columns.php</p> <?php }) ?>   </div> <?php }); include './root.php'; // наследуем от root.php

index.php — страница сайта, верхний шаблон:

<?php require_once '../src/InheritTpl.php';  block('title', function () { ?> 'title' :: index.php <?php }); block('side', function () { ?>   <p>'side' :: index.php</p> <?php });  block('main', function () { ?>   <div id="main-index"> <!-- Обернём содержимое от родителя -->     <?php super() /* тут выводим контент из родительского блока */?>   </div> <?php }); block('main', function () { /* Ещё раз тот же блок, почему бы и нет? */?>   <div id="main-index"> <!-- И ещё раз обернём содержимое -->     <?php super() ?>   </div> <?php }); // А 'footer' пусть остаётся как был include './two-columns.php'; 

Результат рендеринга (отформатирован для читабельности):

<!DOCTYPE html> <html>   <head>     <title> 'title' :: index.php </title>   </head>   <body>     <div id="root">       <div id="two-columnts">         <div id="main">           <div id="main-index"> <!-- Обернём содержимое от родителя -->             <div id="main-index"> <!-- И ещё раз обернём содержимое -->               <p>'main' :: two-columns.php</p>             </div>           </div>         </div>         <div id="side">           <p>'side' :: index.php</p>         </div>       </div>       <div id="footer">         <p>'footer' :: two-columns.php</p>       </div>     </div>   </body> </html>

Хотелки наследования удовлетворены. Но вот интересно, удалось ли сделать эту штуку быстрее?

Перепишем пример выше под библиотеку phpti. Дадим ей небольшую фору, т.к. в примере нет тяжеловесных переопределяемых блоков.

Сравнивать будем время 10,000 рендеров на PHP 8.0.2 и процессоре 3.6ГГц.

  • phpti: 0.831 секунд

  • сабж: 0.353 секунд

В качестве вывода можно сказать что размер библиотеки удалось сократить в 10 раз, при этом скорость работы механизма наследования увеличилась как минимум в 2 раза.

Посмотреть исходный код можно тут.

ссылка на оригинал статьи https://habr.com/ru/post/559896/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *