В этой статье рассмотрим Twig теги - конструкции шаблонизатора, которые выполняют разнообразные задачи, такие как: условные операторы, циклы, экранирование данных, создание макросов, использование наследования шаблонов, запись в переменные и многое другое. В общем виде они записываются с помощью открывающего тега и закрывающего (именуется префиксом end). Закрывающий тег нужен не для всех конструкций. Пример абстрактного тега выглядит так:
{% tag %} {% endtag %}
Тег if в Twig сравним с условным оператором if в PHP. В простейшей форме вы можете использовать его, чтобы проверить выражение на истинность:
{% if label_display == "after" %} {{ label }} {% endif %}
Вы также можете проверить массив или строку на существование:
{% if page.footer_fifth %}
{% endif %}Twig шаблонизатор предполагает дополнительные проверки в условиях:
empty - проверяет, является ли переменная пустой:
{# возвращает true, если переменная foo равна null, false, пустой массив, или пустая строка #} {% if foo is empty %} ... {% endif %} {# возможен вариант с оператором отрицания not #} {% if foo is not empty %} ... {% endif %}
even и odd - возвращает true если данное число является четным или нечетным, соответственно:
{{ if var is even }} ... {% endif %} {{ if var is odd }} ... {% endif %}
iterable - проверяет, является ли переменная массивом или объектом (экземпляром класса, имплементирующего интерфейс Traversable):
{% if users is iterable %} {% for user in users %} Hello {{ user.name }}! {% endfor %} {% else %} {# users вероятно являетcя строкой #} Hello {{ users }}! {% endif %}
null - возвращает true, если переменная равна NULL :
{{ if var is null }} ... {% endif %}
constant - проверяет, имеет ли переменная точно такое же значение, как константа. Вы можете использовать либо глобальные константы, либо константы класса:
{% if status is constant("MARK_NEW") %} {{ "New"|t }} {% elseif status is constant("MARK_UPDATED") %} {{ "Updated"|t }} {% endif %}
divisible - проверяет, делится ли переменная на число:
{% if loop.index is divisible by(3) %} ... {% endif %}
same as - проверяет, указывают ли две переменные на одну ячейку памяти. Аналогичен оператору эквивалентности === в php:
{% if foo.attribute is same as(false) %} foo.attribute эквивалентен значению "false" {% endif %}
defined - проверяет, определена ли переменная:
{# defined работает как с именами переменных #} {% if foo is defined %} ... {% endif %} {# так и с атрибутами переменных #} {% if foo.bar is defined %} ... {% endif %} {% if foo["bar"] is defined %} ... {% endif %}
В условиях можно использовать логические операторы and, or, not:
{% if not page %} {% if foo and bar %} {% if foo or bar %} {# Возможны и сложные составные условия #} {% if (a and b) or (c and d) %}
В условиях имеется возможность использовать регулярные выражения
{% if phone matches "/^[\\d\\.]+$/" %} {% endif %}
Можно также проверить, начинается ли или заканчивается строка с определенного символа:
{% if "Drupal" starts with "D" %} {% endif %} {% if "Drupal" ends with "l" %} {% endif %}
Также вы можете использовать более сложные выражения if-elseif (кстати switch-case в Twig отсутствует ):
{% if rows %}
Тег for аналогичен конструкции for в php и осуществляет "перебор" элементов массива или свойств объекта, если объект является экземпляром класса, имплементирующего Traversable интерфейс.
В качестве примера рассмотрим шаблон поля и в нем перебор множественных значений поля:
{% for item in items %}
Переменные, доступные внутри цикла:
В отличие от PHP нельзя прервать или продолжить цикл (continue и break). Однако можно создавать правила, позволяющие пропускать элементы по условиям for-if . В примере ниже неактивные пользователи пропускаются в итерациях:
Также в Twig возможна конструкция for-else для выполнения действий, если массив или объект оказался пустым:
В Twig также возможны итерации с перебором и ключей, и значений
{% for key, value in values %}
Наследование шаблонов - одна из самых интересных особенностей в Twig. Наследование позволяет создавать базовые родительские шаблоны, которые потом можно будет использовать в дочерних шаблонах, при этом весь код родительских шаблонов доступен и в дочерних, а кроме того есть возможность переопределять некоторые блоки родительских шаблонов. Рассмотрим теги, которые позволяют реализовать наследование:
Рассмотрим использование наследования и тега extends на примере шаблона для блока block.html.twig и его дочерних шаблонов. Базовый родительский шаблон блока содержит код в упрощенном виде:
В шаблоне прописан код, который будет выводиться и в дочерних шаблонах. Содержимое шаблона, которое доступно для переопределения в дочерних шаблонах находится внутри тегов block. У каждой секции block имеется свое имя, которое будет использовано при переопределении. В данном примере есть только один блок с именем content. Весь код вне тегов block будет выведен как есть и его переопределить уже нельзя. Если в дочернем шаблоне вывод блока не будет переопределен, то код блока будет взят из базового шаблона. В качестве дочернего шаблона рассмотрим шаблон для блока с вкладками (в Drupal 8 они вынесены в отдельный блок) block--local-tasks-block.html.twig:
{# Будет унаследован block.html.twig из текущей темы #} {% extends "block.html.twig" %} {% block content %} {% if content %} {% endif %} {% endblock %}
Наследовать шаблоны можно также из модулей и других тем:
{# Будет унаследован block.html.twig из модуля block (core/modules/block/templates/block.html.twig) #} {% extends "@block/block.html.twig" %} {# Будет унаследован block.html.twig из темы classy из каталога block/ (core/themes/classy/templates/block/block.html.twig) #} {% extends "@classy/block/block.html.twig" %}
При переопределении блока родительского шаблона, содержимое из родительского блока перезаписывается, но если в блоке нужно выводить содержимое родительского блока, то используется Twig функция parent()
{% block content %}
Блоки используются для реализации механизма наследования в качестве заполнителей как в родительском шаблоне, так и в дочерних (подробнее в разделе тега ). Имена блоков должны состоять из цифр, букв и символов подчеркивания. Использование тире не допускается.
Макросы можно сравнить с функциями в языках программирования. Они нужны для многократного использования часто используемых частей html кода и при этом макросы могут принимать параметры, что позволяет динамически изменять вставляемый код. Статический же блок с html кодом можно сохранить с помощью тега set. Макросы отличаются от php функций следующими особенностями:
Как и php функции макросы не имеют доступа к глобальным переменным шаблона. Для передачи переменных из шаблона при необходимости нужно использовать специальная переменную _context.
Тег macro определяет новый макрос. В качестве простейшего примера рассмотрим макрос, который рендерит элемент формы input, при этом input type, name, value, size макрос содержит в качестве параметров (type и size задаются по умолчанию):
{% endmacro %}
{# Импортируем макросы из forms.html.twig в текущий шаблон #} {% import "forms.html" as forms %} {# Вызываем макрос #}
{{ forms.input("username") }}
Если макрос определен и используется в одном и том же шаблоне, то для его импорта используется специальная переменная _self.
{% import _self as forms %}
{{ forms.input("username") }}
Если нужно использовать макрос в другом макросе, но в том же самом шаблоне, то макрос импортируется локально внутри другого макроса:
{% macro input(name, value, type, size) %} {% endmacro %} {% macro wrapped_input(name, value, type, size) %} {% import _self as forms %}
В Drupal 8 в шаблоне menu.html.twig макрос menu_links() определяется и используется в одном и том же шаблоне и дополнительно сам макрос используется рекурсивно внутри самого себя для вывода вложенных пунктов меню:
{% import _self as menus %} {{ menus.menu_links(items, attributes, 0) }} {% macro menu_links(items, attributes, menu_level) %} {% import _self as menus %} {% if items %} {% if menu_level == 0 %}
Тег import импортирует все макросы из указанного шаблона:
{# forms.html.twig #} {# Описываем макрос input() #} {% macro input(name, value, type, size) %} {% endmacro %} {# Описываем макрос textarea() #} {% macro textarea(name, value, rows, cols) %} {% endmacro %} {# Импортируем в другой шаблон макросы из forms.html.twig #} {% import "forms.html" as forms %}
{{ forms.textarea("comment") }}
Тег from импортирует только указанные макросы из шаблона:
{# Импортируем из forms.html.twig макросы: input() с алиасом input_field и textarea() #} {% from "forms.html" import input as input_field, textarea %}
{{ textarea("comment") }}
Тег include включает в себя имя шаблона и возвращает отображаемое содержимое этого файла в текущий шаблон. Все переменные текущего шаблона будут доступны в подключаемых шаблонах. С помощью include можно некоторые блоки кода вынести в отдельные шаблоны:
{# header.html.twig #}
Можно добавить дополнительные переменные, передавая их после with:
{# template.html.twig будет иметь доступ к переменным из контекста текущего шаблона и добавленным переменным #} {% include "template.html" with {"foo": "bar"} %} {% set vars = {"foo": "bar"} %} {% include "template.html" with vars %} {# Можно также добавлять несколько переменных #} {% include "template.html" with {"foo": var1, "bar" : var2} %} {# Будут доступны в template.html.twig переменные foo и bar #}
Или можно отключить доступ к контексту путем добавления only.
{# Только foo переменная будет доступна в template.html.twig #} {% include "template.html" with {"foo": "bar"} only %} {# В template.html.twig доступа к переменным не будет #} {% include "template.html" only %}
Вы можете отменить include при помощи инструкции ignore missing, и тогда Twig будет игнорировать подключение несуществующего шаблона. Размещается инструкция ignore missing сразу за именем шаблона:
{% include "sidebar.html" ignore missing %} {% include "sidebar.html" ignore missing with {"foo": "bar"} %} {% include "sidebar.html" ignore missing only %}
Можно предоставить и список шаблонов, которые проверяются на предмет существования до включения.
{% include ["page_detailed.html", "page.html"] %} {% include ["special_sidebar.html", "sidebar.html"] ignore missing %}
Пример использования include в Drupal 8 можно посмотреть в шаблоне темы Classy links--node.html.twig:
{% if links %}
Внутри шаблонов можно создавать переменные с помощью тега set:
{# Записываем в переменную строку #} {% set foo = "foo" %} {# Записываем в переменную массив #} {% set foo = %} {# Записываем в переменную ассоциативный массив #} {% set foo = {"foo": "bar"} %} {# символ ~ осуществляет конкатенацию строк #} {% set foo = "foo" ~ "bar" %} {# Результат: foo содержит "foobar" #} {% set foo, bar = "foo", "bar" %} {# Создано две переменных foo и bar #}
С помощью тега set в переменную можно записать блок кода:
{% set foo %}
Тег spaceless можно использовать для удаления пробелов между HTML-тегами, а также содержимого внутри HTML-тегов или пробелов в простом тексте:
{% spaceless %}
В зависимости от того, включено или нет автоматическое экранирование, вы можете отметить фрагмент шаблона для экранирования или же отключить эту функцию, используя тег autoescape (в Drupal 8 автоэкранирование включено в контексте html):
{% autoescape %} Автоэкранирование в этом блоке осуществляется методами HTML. {% endautoescape %} {% autoescape "html" %} Автоэкранирование в этом блоке осуществляется методами HTML. {% endautoescape %} {% autoescape "js" %} Автоэкранирование в этом блоке осуществляется методами js. {% endautoescape %} {% autoescape false %} Автоэкранирование отключено. Содержимое блока выводится без изменений. {% endautoescape %}
Тег verbatim маркирует секцию и сообщает шаблонизатору не обрабатывать содержимое блока:
{% verbatim %}
Тег filter позволяет применять стандартные фильтры шаблонизатора Twig для блоков данных шаблонов. Просто разместите код внутри секции "filter":
{% filter upper %} Drupal 8 {% endfilter %} {# Результат: "DRUPAL 8" #}
Можно также создавать комбинации фильтров:
{% filter lower|escape %} SOME TEXT {% endfilter %} {# Результат: "some text" #}
Если в теге используется фильтры escape или e, то drupal их заменит на свой фильтр drupal_escape .
Тег do работает так же, как стандартное выражение с переменной {{ var }} , с той разницей, что ничего не выводится. На практике применим, если нужно вызвать функцию или метод объекта, которые что либо могут возвращать или выводить, но в выводе шаблона эти данные не нужны отображать.
{% do 1 + 2 %} {% do product.delete() %}
Тег trans специфичен для Drupal 8. Этот тег позволяет переводить текст (используя токены) внутри тега используя функцию t() или TranslationManager::formatPlural() , если внутри тега использована конструкция {% plural ... %} :
{% trans %}Submitted by {{ author.name }} on {{ node.date }}{% endtrans %}
Мы разобрались с конфигами и значениями параметров. Сегодня научимся выводить настройки в браузере. А чтобы не было скучно просто писать html, подключим к проекту шаблонизатор Twig. Ему и будет посвящена основная часть статьи.
У Twiga отличный функционал и простой синтаксис. В дебри лезть не будем, нам хватит и малой части его возможностей. Начинаем.
Хотел скачать последнюю версию twig-a 2.x, но обломался. Он ставится через composer, а я в нем не шарю. Подозреваю, что composer мало чем отличается от фронтовых npm или bower, но погружаться сейчас не будем. Кто в теме, сделают composer require "twig/twig:^2.0" и без меня, а я как нормальный php-шник скачиваю версию 1.x через Download zip и не парюсь. Качать отсюда https://github.com/twigphp/Twig/tree/1.x
Ищем твигу местечко в проекте. Создадим папку admin/lib/Twig и закинем туда библиотеку. Идеально, смогли и без composer-a.
Идем дальше. Твигу нужна папка cache, куда он может свободно записывать отрендеренные шаблоны. Дискуссионный вопрос, куда определяют папку кэша нормальные пацаны? Я без фантазии создал прямо в admin - admin/cache. Ну и бог с ней. Главное, убедитесь, что в нее разрешена запись юзеру www-data. Если сидите на винде, то пофиг. А если нет, то смените владельца и дайте права на запись в cache
$ sudo chown -R www-data:www-data ./cache $ sudo chmod -R 755 ./cache/
Последнее, что нужно твигу - знать, где брать шаблоны. Для этого сделаем папку admin/templates и закинем в нее index.html c содержимым Hello, {{name}} . Ага, уже постигаем магию. Значение name будет передаваться в шаблон извне, из php-файла.
Давайте в admin/index.php подключим twig и отрендерим шаблон. Испокон веков автор фигачил разметку в index.php, а сейчас нет. Повзрослел. php-файл займется тем, чем и должен - логикой приложения. Даже звучит приятно, значит, пора реализовывать.
// Подключаем шаблонизатор require_once "./lib/Twig/Autoloader.php"; // Инициализируем Twig Twig_Autoloader::register(); $loader = new Twig_Loader_Filesystem("./templates"); $twig = new Twig_Environment($loader, array("cache" => "./cache", "debug" => true)); // Рендерим шаблон echo $twig->render("index.html", array("name" => "Twig"));
В первой строке подключаем сам твиг. Дальше инициализация.
В документации я прочитал, если возникнет Непредвиденный Случай и почему-то НЕ СРАБОТАЕТ, то напишите Twig_Autoloader::register();
Я человек-удача и тот самый случай поймал. Написал, что велено, и все стало хорошо. Надеюсь, Вам повезет больше и вместо 10 строк уложитесь в 9.
Дальше в $loader и $twig загружаем среду или что-то такое нужное для шаблонизатора. Указываем в параметрах путь к папкам шаблонов и кеша и важный параметр debug=true. На боевом сайте debug убирайте, но пока оставьте. Иначе при изменении шаблона в templates/index.html твигу будет наплевать и он сразу возьмет срендеренный из кэша. И будем удивляться, чо это шаблон правим, а в браузере не меняется.
Итак, обновим страницу и видим текст Hello, Twig . Поздравляю, наш первый шаблон отработал. Но прежде чем наполнять его полезным содержимым, немного изменим index.php. А точнее подключим класс админки и в шаблон отдадим не name=Twig, а настройки. Получится вот так.
// Подключаем шаблонизатор и класс админки require_once "./lib/Twig/Autoloader.php"; require_once "./admin.class.php"; // Инициализируем Twig Twig_Autoloader::register(); $loader = new Twig_Loader_Filesystem("./templates"); $twig = new Twig_Environment($loader, array("cache" => "./cache", "debug" => true)); // Создаем объект админки $admin = new Admin(); // Рендерим шаблон echo $twig->render("index.html", array("settings" => $admin->getSettings()));
В предыдущей статье засветился прототип. На него и ориентируемся. Нам понадобится заготовка html-документа и простая форма с названиями параметров и значениями.
Конечно, форма будет на bootstrap. Однажды я раздуплюсь и покажу какой-нибудь другой css-фреймворк, но пока лень. Оправдываю себя тем, что мы же с вами не css-ы верстаем, а ПРИЛОЖЕНИЯ ПРОГРАММИРУЕМ. Пока отмазка работает, возвращаемся к шаблону.
Заготовку беру с сайта bootstrap. В head скопипастим такое
А body придется писать самим.
Выглядит как обычная бутстраповская форма, но с твиговскими вставками.
Разберем по очереди.
{% for item in settings %}
Перебираем в цикле настройки и для каждой выводим label и input в форме.
item.key используется и как айди, и как название name инпута, и как значение for в label. Такой важный нужный key.
item.title - заголовок настройки.
А это текстовое поле со значением.
Теперь обновляем страницу и видим список настроек с нужными значениями. Красота! Поменяйте значения полей в config/values.json. Работает, опять красота.
На этом позитивном предложении автор закончил бы статью, но нельзя. Потому что внимательный читатель скажет: стоп, а картиночка-то не та! С прототипом не сходится. А любознательный читатель еще и спросит: а зачем в первом уроке столько мутили с конфигами и разными типами данных? Чтобы сейчас выводить их тупо в инпуты? Конечно, читатель прав. Поэтому автор сходит выпить чаю и напишет еще пару абзацев.
Итак, продолжаем. Мы хотим выводить разные инпуты-селекты в зависимости от типа найстройки. В этом помогут условия twig и поле type из конфига настроек. В наличии 4 типа настроек: text, number, checkbox и select. Под каждую из них делаем условие. Вместо
пишем так
{% if item.type == "text" %} {% endif %} {% if item.type == "number" %} {% endif %} {% if item.type == "checkbox" %}
{% endif %} {% if item.type == "select" %} {% for option in item.list %} {{ option }} {% endfor %} {% endif %}
Вроде прям много и развесисто, но посмотрим ближе и станет яснее.
Теперь еще раз обновим страницу. У меня получилось так
Вот теперь все. Можно поиграть с настройками и значениями в json-файлах и посмотреть, как оно успешно раскидывается по формочке.
Если прониклись твигом и хотите узнать больше, то вот документация на русском. https://x-twig.ru/intro/
Следующий урок будет заключительным по теме "админка на файлах". Мы напишем js-код, который отправит данные на бекенд, и php-код, который эти данные сохранит. Бонусом внедрим уведомления, чтобы пользователь видел, не забыл ли он нажать кнопочку сохранить.
Небольшой опрос напоследок и до встречи!
, | |
Скачайте инсталлятор Composer в root-папку приложения (twig):
cd /var/www/twig
curl -s http://getcomposer.org/installer | php
Это загрузит файл по имени composer.phar, который является исполняемым файлом Composer. Затем создайте файл с названием composer.json:
nano composer.json
И внесите в него следующий код:
{
"require": {
"twig/twig": "1.*"
}
}
Благодаря этому коду приложение будет зависить от движка шаблонов Twig. Теперь давайте использовать команду composer, чтобы установить зависимости:
php composer.phar install
Теперь файлы Twig загружены в папку vendor/Twig. Если добавить других зависимостей, они также будут установлены в папке vendor.
Теперь нужно «подключить» этот файл к приложению. Опять же, можно использовать функцию автозагрузчика Composer, чтобы загрузить все установленные зависимости. Откройте файл app.php:
nano app/app.php
Удалите все его содержимое и внесите эту строку:
Данная строка загрузит автозагрузчик Composer, который, в свою очередь, загрузит Twig. Если Twig был установлен другим способом, понадобится автозагрузчик Twig:
require_once "/path/to/lib/Twig/Autoloader.php";
Twig_Autoloader::register();
При использовании Composer в этом нет необходимости.
Под командой require нужно поместить файл шаблона Twig и задать переменную, которую нужно вывести на страницу. Вставьте следующий код:
render("page.html", array("text" => "Hello world!")); ?>
Во-первых, это создаст объект загрузчика, которому передается путь к папке, содержащей файлы шаблонов (пустая на данный момент папка views была создана ранее). Кроме того, этот код также создаст объект среды, в котором хранится конфигурация. В завершение, строка render объекта среды загружает файл шаблона, заданный в качестве первого аргумента и визуализирует его с учетом переменных, заданных в качестве второго аргумента. Теперь нужно создать файл page.html, в который можно внести переменные, содержащие текст, который нужно отобразить.
nano views/page.html
В него внесите следующее:
Если сохранить этот файл и перезапустить файл app.php в браузере, на страницу будет выведен текст, взятый в теги Н1. Теперь, создав этот файл, можете поэкспериментировать с ним. Переменные, заданные через функцию render, теперь можно вносить в фигурные скобки.
Ради эксперимента можете передать несколько переменных шаблону, использовать их в управляющих структурах, обработать их с помощью функций шаблонов или применить фильтры Twig.
Это руководство продемонстрировало установку Twig через Composer, а также вывод на экран простых переменных с помощью отдельных файлов шаблонов. этой серии более подробно рассматривает использование синтаксиса Twig для разработки файлов шаблонов.
Tags: ,Используются для подготовки шаблонов печатных форм, писем, SMS и оповещений. Представляют из себя html-верстку с вкраплениями twig-разметки, поэтому для создания шаблонов необходимы базовые знания html и twig. В системе, как правило, есть ряд заранее подготовленных шаблонов, которые вы можете брать за основу.
Примеры обращений:
{% if order.fromApi %} {{ order.getNickName() }} {% endif %}
Объекты связаны между собой через поля, т.е., обращаясь к полю одного объекта, можно получить другой объект. В таких случаях описание поля в справочнике является ссылкой на объект, который возвращается при обращении к полю.
Пример обращения:
{% if order.getDeliveryType().getCode() == "russian-post" %} {{ order.trackNumber }} {% endif %}
Могут возникать ситуации, когда требуется отменить генерацию шаблона. Например, мы на триггере хотим отправлять письмо с товарными рекомендациями, но, если никаких рекомендаций для клиента не найдено, то генерацию шаблона письма и его отправку производить не надо. Для отмены следует использовать тег {% cancel %} .
Пример использования:
{% set recom = best_selling_products() %} {% if recom|length > 0 %} {# выводим рекомендуемые товары #} {% else %} {% cancel %} {% endif %}
Если срабатывает {% cancel %} , то в это будет зафиксировано:
В теге также можно указать сообщение, которое будет зафиксировано в .
Пример использования с указанием сообщения:
{% set recom = best_selling_products() %} {% if recom|length > 0 %} {# выводим рекомендуемые товары #} {% else %} {% cancel "Не найдено рекомендованных товаров" %} {% endif %}
В журнале действий в этом случае будет следующее:
В случае отмены генерации шаблона письма, SMS или печатной формы, которые генерируются вручную, пользователю будет выведено сообщение, указанное в теге {% cancel %} .
Для вывода данных из пользовательского поля используйте метод getCustomField . Пример:
{{ order.getCustomField("some_code") }}
В примере some_code - символьный код пользовательского поля.
Речь о проблеме в случае, когда вы видите следующее сообщение об ошибке:
Calling "__tostring" method on a "DateTime" object is not allowed
Дело в том, что при выводе в twig-шаблоне полей заказа (или клиента), которые содержат дату, требуется применять фильтр date , чтобы указать формат вывода даты.
{{ order.createdAt|date("d.m.Y") }}
Речь о проблеме в случае, когда вы видите подобное сообщение об ошибке:
Calling "m" property on a "DateInterval" object is not allowed
Дело в том, что при выводе в twig-шаблоне полей, которые содержат интервал дат, требуется применять фильтр date , чтобы указать формат вывода.
{{ period|date("%m") }}
Подробное описание форматов вывода DateInterval можно найти по ссылке: http://php.net/manual/ru/dateinterval.format.php
Для вывода текущей даты используйте фильтр date следующим образом:
{{ "now"|date("d.m.Y") }}
Вы можете воспользоваться сервисом http://www.barcodes4.me/apidocumentation . Пример подстановки данных: