Шаблонизатор на PHP. Часть 1
Coding
Мне в голову пришла идея написать простой шаблонизатор PHP, в котором файл шаблона открывается в потоке, изменяется и преобразуется в корректный PHP-код. А затем другой поток используется, чтобы смоделировать файл, который отображается через буфер вывода и функцию include. Таким образом исключаются временные файлы и легко могут быть созданы фильтры, делая синтаксис шаблона простым для изменения.
Синтаксис
Пример реализации механизма шаблонизатора находится здесь —Affinity4/Template. Он поддерживает PHP, поэтому синтаксис шаблонов не является обязательным. На это меня вдохновил Sass.
Вторая ключевая особенность – это то, что синтаксис шаблона не будет отображаться без применения механизма шаблонов или в случае ошибки компиляции потока в PHP. Это означает, что если вы решите использовать другой язык шаблонов или включите файлы напрямую, то при просмотре страницы вы не увидите блоков {{ var }} или @foreach(thingy in thingys), оставленных в коде.
Третьей особенностью является то, что синтаксис по умолчанию должен работать во всех IDE, текстовых редакторах и быть понятен всем, знающим HTML.
Чтобы шаблонизатор соответствовал второму и третьему требованию, нужно использовать комментарии HTML.
Вот пример:
<h1><!-- :title --></h1>
<!-- @if :show_list is true -->
<ul>
<!-- @each :item in :items -->
<li><!-- :item --></li>
<!-- @/each -->
<ul>
<!-- @/if -->
Недостаток заключается в том, что здесь больше кода. Но он может быть быстро написан, используя горячие клавиши комментариев в редакторе или IDE (обычно Ctrl + / или Cmd + /).
Достоинства шаблонизатора:
- Поддержку IDE/редакторов по умолчанию.
- Возможность использования чистого PHP-код. Это позволяет быстро усвоить механизм.
- Специфический язык шаблона может быть полностью проигнорирован любым, кто хочет работать только над разметкой.
Наш механизм шаблонов не будет включать в себя инструменты макетов или блоков, которые есть в Twig, Blade, Latte и т.д. Это важные средства, и я планирую реализовать их в будущем. Но нам они не нужны.
Структура проекта
Мы поместим всё в два класса: один для обработки правил синтаксиса (регулярные выражения), другой — для открытия файлов исходного кода, их компиляции в PHP, выделения переменных и вывода.
Звучит запутанно? Но с помощью vfsStream это становится тривиальной задачей по сравнению с традиционным методом создания лексеров/парсеров, токенизаторов и разработкой некоторой формы AST(абстрактного синтаксического дерева).
Мы используем менеджер зависимостей Composer, чтобы подключать vfsStream и загружать наши собственные классы.
composer require mikey179/vfsStream
Далее создадим в корне проекта папку под названием src. Внутри нее разместим два файла классов (Engine.php и Syntax.php). Структура папок должна выглядеть следующим образом:
template
|-- composer.json
|-- src
|-- Engine.php
|-- Syntax.php
|-- views
|-- home.php
Затем я записываю всё в файл index.php, чтобы увидеть правильность реализации. Для механизма шаблонов это достаточно просто. Поэтому можно сделать следующее:
Файл: index.php
<?php
use TemplateEngine; require_once __DIR__ . '/vendor/autoload.php'; $template = new Engine;
$template->render('views/home.php', [
'show_title' => true,
'title' => 'Home'
]);?>
Чтобы получить рабочую версию шаблонизатора, нужно следующее:
- Метод прорисовки шаблона (render).
- Метод render должен получать путь к представлению в качестве первого аргумента.
- Необязательный массив параметров может передаваться в представление.
- Параметры должны быть выделены в переменные, если второй аргумент – не пустой массив.
- Загрузка представления в vfsStream для дальнейшей обработки.
- Включение обработанного (скомпилированного) «файла» в буфер вывода для отображения.
- Файл представления: views/home.php.
Сначала создадим наше представление с простым PHP кодом, чтобы убедиться, что переменные передаются в представление:
Файл: views/home.php
<?php if ($show_title) : ?>
<h1><?php echo $title; undefined?></h1>
<?php endif; ?>
Теперь можно сфокусироваться на простейшей реализации класса шаблонизатора на PHP(Engine.php).
Файл: src/Engine.php
namespace TemplateEngine; use orgbovigovfsvfsStream; class Engine
{
public function render($view, $params = [])
{
// 1. Выделить параметры при непустом массиве params
// 2. Если представление существует, включить его в буфер вывода
// 3. Если файл представления не найден, вывести исключение
}
}?>
Так мы получим рабочую версию метода render, хотя и без желаемой функциональности.
Перед тем, как двигаться дальше, нужно добавить папку src в автозагрузчик PSR-4, расположенный в файле composer.json:
Файл: composer.json
{
<span class="strong">undefinedundefinedundefined "autoload": {</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefined "psr-4": {</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined "Template": "src/"</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefined }</span>
<span class="strong">undefinedundefinedundefined },</span>
"require": {
"mikey179/vfsStream": "^1.6"
}
}
Теперь требуется повторно сгенерировать файлы автозагрузки и файл composer.lock:
composer dumpautoload
Реализуем логику получения представления для отображения с параметрами, которые мы передали.
Файл: src/Engine.php
<?php
...
class Engine
{
public function render($view, $params = [])
{
// 1. Выделить параметры при непустом массиве params
<span class="strong">if (!empty($params)) extract($params);</span> // 2. Если наше представление существует, включить его в буфер вывода
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefined if (file_exists($view)) {</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined ob_start();</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined include $view;</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined ob_get_flush();</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefined } else {</span>
// 3. Если файл представления не найден, вывести исключение.
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined throw new Exception(sprintf('Файл %s не найден.', $view));</span>
<span class="strong">undefinedundefinedundefinedundefinedundefinedundefinedundefined }</span>
}
}
?>
Запустите локальный сервер для PHP. Перейдите в корень проекта и откройте командную строку. В командной строке наберите:
php -S localhost:80
Вы увидите сообщение о том, что сервер запущен. Откройте браузер, перейдите по адресу http://localhost/ и вы должны увидеть:

Теперь мы знаем, что метод render будет работать, так как ожидается. В следующей части мы подключим vfsStream.
Не забывайте ставить 👍 если вам понравилась и подписаться на канал