TPL-шаблонизатор в Albireo CMS
TPL-шаблонизатор используется для того, чтобы упростить использование связки PHP+HTML. Хотя PHP сам по себе неплохой шаблонизатор, но из-за того, что он используется символы <
и >
, это создаёт определённую путаницу с этими же символами в HTML.
Другая возможность TPL-шаблонизатора в том, чтобы упростить вывод готовых данных в цикле, где формируется html-разметка.
Для работы с TPL-шаблонизатором требуются хотя бы начальные знания PHP.
TPL-шаблонизатор в Albireo CMS достаточно мощный, но в то же время быстрый и не настолько ресурсозатратный, как другие аналогичные шаблонизаторы. В процессе разработки я ориентировался на лучшие идеи от:
Существенное отличие от этих проектов заключается в том, что шаблонизатор Albireo CMS не пытается заменить PHP-код, а работает как дополнительные возможности PHP. Именно поэтому в Albireo CMS в шаблонизаторе можно использовать абсолютно любой php-код без ограничений.
Что касается синтаксиса TPL-шаблонизатора в Albireo CMS, то он ориентируется на синтаксис фигурных скобок — это уже привычный для программистов подход.
Основные принципы
Работа TPL-шаблонизатора заключается в двух действиях. Первое — это сформировать исходные данные для вывода и второе — передать их в tpl-шаблон.
Поскольку практически все tpl-шаблоны используют css-классы шаблона, то их следует размещать в каталоге шаблона tpl
.
Формально же, это не обязательное правило и эти файлы можно размещать в произвольном месте вашего сайта.
Чтобы понять принцип действия TPL-шаблонизатора покажу небольшой демо-пример.
В теле страницы (например в hello.php
) создадим массив данных для вывода:
# массив $myData = [ 'h1' => 'lorem', 'h2' => 'ipsum', 'h3' => 'dolor', ]; # отправим его на вывод echo tpl(data: $myData, tpl: TPL_DIR . 'my.php');
Файл my.php
:
echo $h1; echo $h2; echo $h3;
Если обновить страницу, то мы получим вывод loremipsumdolor
.
Таким образом $myData
был распакован на php-переменные, которые совпадают с его ключами.
Файл my.php
представляет собой обычный php-файл, именно поэтому мы используем чистый php-код. Но если нам нужно использовать html-разметку, то код будет сложнее читать и именно здесь можно использовать синтаксис TPL-шаблонизатора.
Изменим my.php
:
<h1>{{ $h1 }}</h1> <h2>{{ $h2 }}</h2> <h3>{{ $h3 }}</h3>
Теперь это не php-код, а обычный текст с html-тэгами. Конструкция {{ }}
эквивалента echo
, то есть шаблонизатор сам сделал замены текста на php-код и выполнил его.
Если исходные данные содержат html-спецсимволы, например кавычки и угловые скобки, то можно использовать другой вариант вывода:
<h1>{* $h1 *}</h1> <h2>{* $h2 *}</h2> <h3>{* $h3 *}</h3>
В этом случае помимо echo
шаблонизатор прогонит вывод через функцию htmlspecialchars()
.
Если в tpl-файле нужно оставить комментарий, то он обрамляется через {# #}
:
{# вывод заголовков #} <h1>{* $h1 *}</h1> <h2>{* $h2 *}</h2> <h3>{* $h3 *}</h3>
Если же нужно вывести текст без обработки, например код JavaScript, то его следует обрамить в {@verbatim}
:
{# вывод заголовков #} <h1>{* $h1 *}</h1> <h2>{* $h2 *}</h2> <h3>{* $h3 *}</h3> {@verbatim} код без обработки {@endverbatim}
Хотя в тексте tpl-файла можно использовать обычные php-вставки, иногда удобней использовать их замены:
{@ ваш php-код @} -> эквивалентно <?php ваш php-код ?> {?php ваш php-код ?} -> эквивалентно <?php ваш php-код ?>
Если ключ исходного массива будет содержать символы, которые не могут быть именами переменных, то шаблонизатор их заменит символом _
.
$myData = [ 'image-home' => 'адрес', ];
В этом примере в tpl-файле будет создана переменная $image_home
, потому что $image-home
— это некорректное имя переменной в PHP.
Вложенные циклы
Частой задачей является выполнение вложенных циклов. Например у нас есть массив страниц и нужно каждую из них вывести через tpl-шаблонизатор.
Схематически это можно представить как многомерный массив:
$myPages = [ 'file1.php' => [ 'slug' => 'file1', 'title' => 'Страница 1', ], 'file2.php' => [ 'slug' => 'file2', 'title' => 'Страница 2', ], ];
Для того, чтобы вывести этот массив потребуется два вложенных цикла. Внешний будет перебирать ключи file1.php
и file2.php
, а внутренний будет проходится по элементам title
и slug
.
С помощью TPL-шаблонизатора это делается достаточно просто. Рассмотрим демо-пример на рабочих данных вашего сайта.
Вначале получим страницы сайта — они как раз представляют собой аналогичный многомерный ассоциативный массив.
// получим записи с type: blog $files = getPagesField('type', 'blog'); // отсортируем их, чтобы новые оказались вверху $files = sortArray($files, 'date', 'date-desc'); // возьмем срез из первых 3-х записей $files = array_slice($files, 0, 3, true); # pr($files); // можно посмотреть содержимое массива для отладки // отправим эти данные на вывод echo tpl(data: $files, tpl: TPL_DIR . 'my.php');
Теперь напишем код my.php
:
{@loop $DATA} <h1>{* $title *}</h1> <p class="t-italic">{{ $description }}</p> <hr> {@endloop}
Код @loop
запускает цикл по $DATA
— это «внешняя» часть массива $files
. Поэтому внутри конструкции @loop
станут доступны переменные из «внутреннего» цикла. В нашем случае это поля title
и description
.
Проверки isset и empty
Сейчас мы не проверяем существование переменных, предполагая, что они есть, но в большинстве случаев нужно проверить их существование. Для этого используется конструкция @isset
:
{@loop $DATA} <h1>{* $title *}</h1> {@isset $announce} <p class="t-italic">{{ $announce }}</p> {@endisset} <hr> {@endloop}
Здесь мы проверяем переменную $announce
(поле announce
) и выводим только если она есть.
Другой способ проверки — это условие @empty
, которое не только проверяет существование переменной, но и его ненулевое значение (пустая строка, 0, false, null).
{@loop $DATA} <h1>{* $title *}</h1> {@empty! $announce} <p class="t-italic">{{ $announce }}</p> {@endempty} <hr> {@endloop}
В данном случае {@empty! $announce}
— это !empty()
, поскольку нам нужно вывести если не пусто.
Управляющие конструкции if
Команды @isset
и @empty
— это частный случай команды @if
:
{@if xxx} -> if (xxx) : {@if! xxx} -> if (!xxx) : {@else} -> else : {@elseif $a} -> elseif ($a) : {@elseif! $a} -> elseif (!$a) : {@endif} -> endif {@isset $a} -> if (isset($a)) : {@isset! $a} -> if (!isset($a)) : {@endisset} -> endif {@empty $a} -> if (empty($a)) : {@empty! $a} -> if (!empty($a)) : {@endempty} -> endif
С их помощью можно создавать достаточно сложные конструкции:
{@loop $DATA} <h1>{* $title *}</h1> {@empty! $announce} <p class="t-italic">{{ $announce }}</p> {@else} {@empty! $description} <p class="t-italic">{{ $description }}</p> {@else} <p class="t-red">Нет описания...</p> {@endempty} {@endempty} <hr> {@endloop}
Счетчики цикла loop
Внутри цикла @loop
автоматически доступны служебные переменные, которые могут использоваться для управления выводом:
$loopCount - всего элементов в цикле $loopKey - текущий ключ $loopIndex - текущий счётчик $loopRemaining - обратный счётчик $loopOdd - true если это нечётный элемент $loopEven - true если это чётный элемент $loopFirst - true если это первый элемент $loopLast - true если это последний элемент
Например мы не хотим выводить тэг HR после последней записи, а также вывести номер записи в заголовке:
{@loop $DATA} <h1>{{ $loopIndex }}. {* $title *}</h1> {@isset $announce} <p class="t-italic">{{ $announce }}</p> {@endisset} {@if! $loopLast}<hr>{@endif} {@endloop}
Flex-сетка с помощью цикла loop
Сетка flex состоит из общего контейнера и вложенных ячеек. Поэтому контейнер нужно вынести из цикла @loop
.
<div class="flex flex-wrap"> {@loop $DATA} <div class="w30 w100-tablet"> <h3>{* $title *}</h3> {@isset $announce} <p class="t-italic t90">{{ $announce }}</p> {@endisset} </div> {@endloop} </div>
Здесь мы создаём flex-контейнер, а для ячеек выставляем размер 30% ширины для десктопа и 100% ширины для мобильных устройств. То есть получается три ячейки в ряд.
Управление class и style
С помощью @class
можно управлять выводом css-классов. Конструкция состоит из массива, где указываются классы и условие их вывода. Например сделаем красный текст для каждого четного элемента:
{@loop $DATA} <h2 class="{@class ['t-red' => $loopEven]}">{* $title *}</h2> {@endloop}
Если элемент массива содержит только ключ, то он выводится при каждой итерации:
{@loop $DATA} <h2 class="{@class ['t-italic', 't-red' => $loopEven]}">{* $title *}</h2> {@endloop}
Этот код будет эквивалентен:
{@loop $DATA} <h2 class="t-italic {@class ['t-red' => $loopEven]}">{* $title *}</h2> {@endloop}
Аналогично работает конструкция @style
:
{@loop $DATA} <h2 style="{@style ['color: red' => $loopEven]}">{* $title *}</h2> {@endloop}
Генератор yield
С помощью TPL-шаблонизатора довольно просто организовать перебор элементов. Например нужно для каждой записи использовать свои наборы классов. Для этого удобно применить генератор @yieldN
, где N — это произвольный номер генератора.
{@loop $DATA} <h2 class="{@yield1 ['t-red', 't-green', 't-blue']}">{* $title *}</h2> {@endloop}
В данном случае для первого вывода будет использован класс t-red
, для второго — t-green
, для третьего — t-blue
, а для всех последующих ничего не будет выведено.
Генератор @yield
обязательно нумеруется, поскольку таких генераторов может быть множество и нужно их различать.
Проверка полей
Частая задача — проверить отмечено ли поле записи. Например есть поле image-noview
, которое должно принудительно отключать вывод миниатюры (поле image-large
). Для такой проверки используется конструкция @check
:
... {@isset $image_large}{@check! $image_noview} <a href="{{ $page_url }}">{{ hThumb($image_large, width: 900, height: 471, attr: ['class'=>'w100 rounded10 hover-img-gray30']) }}</a> {@endcheck}{@endisset} ...
Здесь первое условие — проверка существования переменной $image_large
и если оно есть, то проверяется отметка {@check! $image_noview}
, которое будет соответствовать полю image-noview: -
.
Если указать {@check $image_noview}
, то это будет соответствовать image-noview: +
.
Условия match
Это новая возможность PHP 8, которая позволяет использовать сразу несколько условий в одном операторе.
{@loop $DATA} <h2>{* $title *}</h2> <p class="t-gray">{@match $loopIndex == 1 => "Первый", $loopIndex == 2 => "Второй", $loopIndex == 3 => "Третий", default => '', }</p> {@endloop}
Вывод контента файла
Если нужно вывести весь файл страницы целиком, то используется @contentFile
:
{@loop $DATA} <div>{@contentFile}</div> {@endloop}
Обратите внимание, что @contentFile
выводит уже полностью готовый текст страницы, включая его заголовок и т.п.
В Albireo CMS эта возможность используется для вывода short-страниц.
Циклы for, foreach, while
Эти циклы повторяют стандартные PHP.
{@foreach $DATA as $url => $num} -> foreach ($DATA as $url => $num) : {@endforeach} -> endforeach {@foreach $DATA as $d} {{ tpl(TPL_DIR . 'loop.php', $d) }} {@endforeach} {@for $i = 0; $i < 10; $i++} -> for ($i = 0; $i < 10; $i++) : {@endfor} -> endfor {@for $i = 0; $i < 10; $i++} The current value is {{ $i }} {@endfor} {@while $i <= 10} -> while ($i <= 10) : {@endwhile} -> endwhile {@continue} -> continue {@break} -> break {@continue $a} -> if ($a) continue {@continue! $a} -> if (!$a) continue {@break $a} -> if ($a) break {@break! $a} -> if (!$a) break {@ $i = 1 @} {@while $i <= 10} {{ $i }} {% $i++ %} {@endwhile}
Языковой перевод
Если стоит задача сделать языковой перевод, то следует использовать @lang
:
... {@isset $announce} __(mar10-tb) {{ $announce }} __(t-right) <a class="button icon-arrow-right" href="{{ $page_url }}">{@lang Read}</a> {@endisset} ...
В данном примере фраза «Read» будет пропущена через функцию lang()
и вернёт её перевод (если он есть) для текущего языка пользователя. Для русского это будет «Читать», для украинского «Читати».
Дополнительные данные для tpl-файла
В некоторых случаях нужно передать в tpl-файл дополнительные данные, которые не будут связаны с исходным массивом данных. Например нужно сделать вывод заголовка блока.
Для этого он указывается в функции tpl()
в аргументе add
как массив:
echo tpl(data: $files, tpl: TPL_DIR . 'my.php', add: ['header' => 'Заголовок блока']);
Соответственно в tpl-файле ключи будут доступны как одноимённые переменные:
{@empty! $header} <h1>{* $header *}</h1> {@endempty} {@loop $DATA} <h2>{* $title *}</h2> ... {@endloop}
Примеры вывода блоков
Tpl-файлы в первую очередь используются для формирования вывода записей. Есть разные алгоритмы вывода записей, поэтому для удобства вы можете посмотреть страницу ваш-сайт/group-samples, где приведены рабочие примеры. Исходный код этого адреса размещен в каталоге страниц в specific/group-samples.php
.
Философия TPL-шаблонизатора Albireo CMS
В отличие от других шаблонизаторов, Albireo CMS не ставит никаких условий и ограничений по использованию любого кода. Задача в том, чтобы предложить разработчику шаблона более удобный инструмент для интеграции программной логики PHP в HTML-разметку. Именно поэтому в TPL-шаблонизаторе Albireo CMS нет неочевидных замен и ограничений на использование php-кода, как это принято в других шаблонизаторах (например в Twig запрещено выполнять свой php-код).
Если возможностей шаблонизатора Albireo CMS недостаточно, то вы всегда можете использовать нативный PHP.