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-шаблонизатора это делается достаточно просто. Рассмотрим демо-пример на рабочих данных вашего сайта.
Вначале получим страницы сайта — они как раз представляют собой аналогичный многомерный ассоциативный массив.
// получим записи, где указываем количество записей на одной странице пагинации $result = getPages(limit: 10); // можно посмотреть содержимое массива для отладки # pr($result); // отправим эти данные на вывод echo tpl(data: $result['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)) :
{@elseisset $a} -> elseif (isset($a)) :
{@elseisset! $a} -> elseif (!isset($a)) :
{@endisset} -> endif
{@empty $a} -> if (empty($a)) :
{@empty! $a} -> if (!empty($a)) :
{@elseempty $a} -> elseif (empty($a)) :
{@elseempty! $a} -> elseif (!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.