Twig теги

Другие статьи серии Twig

В этой статье рассмотрим Twig теги - конструкции шаблонизатора, которые записываются в виде:


{% tag %} {% endtag %}

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

Управляющие конструкции: условные операторы и циклы

Тег if

Тег if в Twig сравним с условным оператором if в PHP. В простейшей форме вы можете использовать его, чтобы проверить выражение на истинность:


{% if label_display == 'after' %}
  {{ label }}
{% endif %}

Вы также можете проверить массив или строку на пустоту:


{% if page.footer_fifth %}
  <div class="site-footer__bottom">
    {{ page.footer_fifth }}
  </div>
{% 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 %}
  <div class="view-content">
    {{ rows }}
  </div>
{% elseif empty %}
  <div class="view-empty">
    {{ empty }}
  </div>
{% else %}
  else output
{% endif %}

Тег for

Тег for аналогичен конструкции for в php и осуществляет "перебор" элементов массива или свойств объекта, если объект является экземпляром класса, имплементирующего Traversable интерфейс.

В качестве примера рассмотрим шаблон поля и в нем перебор множественных значений поля:


{% for item in items %}
  <li{{ item.attributes }}>{{ item.content }}</li>
{% endfor %}

Переменные, доступные внутри цикла:

  • loop.index - Текущая итерация цикла (начинается с 1)
  • loop.index0 - Текущая итерация цикла (начинается с 0)
  • loop.revindex - Количество итераций с конца цикла (начинается с 1)
  • loop.revindex0 - Количество итераций с конца цикла (начинается с 0)
  • loop.first - Флаг равный True, если текущая итерация является первой
  • loop.last - Флаг равный True, если текущая итерация является последней
  • loop.length - Количество элементов в последовательности
  • loop.parent - Родительский контекст (используется для вложенных циклов)

В отличие от PHP нельзя прервать или продолжить цикл (continue и break). Однако можно создавать правила, позволяющие пропускать элементы по условиям for-if. В примере ниже неактивные пользователи пропускаются в итерациях:


<ul>
    {% for user in users if user.status %}
        <li>{{ user.name }}</li>
    {% endfor %}
</ul>

Также в Twig возможна конструкция for-else для выполнения действий, если массив или объект оказался пустым:


<ul>
    {% for user in users %}
        <li>{{ user.name }}</li>
    {% else %}
        <li><em>{{ no_found|t }}</em></li>
    {% endfor %}
</ul>

В Twig также возможны итерации с перебором и ключей, и значений


{% for key, value in values %}
    <li>{{ key }}: {{ value }}</li>
{% endfor %}

Наследование шаблонов в Twig

Наследование шаблонов - одна из самых интересных особенностей в Twig. Наследование позволяет создавать базовые родительские шаблоны, которые потом можно будет использовать в дочерних шаблонах, при этом весь код родительских шаблонов доступен и в дочерних, а кроме того есть возможность переопределять некоторые блоки родительских шаблонов. Рассмотрим теги, которые позволяют реализовать наследование:

Тег extends

Рассмотрим использование наследования и тега extends на примере шаблона для блока block.html.twig и его дочерних шаблонов. Базовый родительский шаблон блока содержит код в упрощенном виде:


<div{{ attributes }}>
  {{ title_prefix }}
  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}
  {{ title_suffix }}
  {% block content %}
    {{ content }}
  {% endblock %}
</div>

В шаблоне прописан код, который будет выводиться и в дочерних шаблонах. Содержимое шаблона, которое доступно для переопределения в дочерних шаблонах находится внутри тегов block. У каждой секции block имеется свое имя, которое будет использовано при переопределении. В данном примере есть только один блок с именем content. Весь код вне тегов block будет выведен как есть и его переопределить уже нельзя. Если в дочернем шаблоне вывод блока не будет переопределен, то код блока будет взят из базового шаблона. В качестве дочернего шаблона рассмотрим шаблон для блока с вкладками (в Drupal 8 они вынесены в отдельный блок) block--local-tasks-block.html.twig:


{# Будет унаследован block.html.twig из текущей темы #}
{% extends "block.html.twig" %}
{% block content %}
  {% if content %}
    <nav class="tabs" role="navigation" aria-label="{{ 'Tabs'|t }}">
      {{ content }}
    </nav>
  {% endif %}
{% endblock %}

Наследовать шаблоны можно также из модулей и других тем:


{# Будет унаследован block.html.twig из модуля block (core/modules/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 %}
  <h3> Header</h3>
  ...
  {{ parent() }}
  {# Выведет содержимое родительского блока #}
{% endblock %} 

Тег block

Блоки используются для реализации механизма наследования в качестве заполнителей как в родительском шаблоне, так и в дочерних (подробнее в разделе тега extends). Имена блоков должны состоять из цифр, букв и символов подчеркивания. Использование тире не допускается.

Макросы в Twig

Макросы можно сравнить с функциями в языках программирования. Они нужны для многократного использования часто используемых частей html кода и при этом макросы могут принимать параметры, что позволяет динамически изменять вставляемый код. Статический же блок с html кодом можно сохранить с помощью тега set. Макросы отличаются от php функций следующими особенностями:

  • аргументы по умолчанию в макросах задаются внутри тела макроса;
  • аргументы в макросах всегда являются опциональными (необязательными);
  • если в макрос передается больше аргументов, чем описано параметров, то все они будут содержаться в переменной varargs.

Как и php функции макросы не имеют доступа к глобальным переменным шаблона. Для передачи переменных из шаблона при необходимости нужно использовать специальная переменную _context.

Тег macro

Тег macro определяет новый макрос. В качестве простейшего примера рассмотрим макрос, который рендерит элемент формы input, при этом input type, name, value, size макрос содержит в качестве параметров (type и size задаются по умолчанию):


{% macro input(name, value, type, size) %}
    <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}

Далее макрос может быть использован в любом шаблоне. Но перед использованием его нужно будет импортировать с помощью тега import:


{# Импортируем макросы из forms.html.twig в текущий шаблон #}
{% import "forms.html" as forms %}

{# Вызываем макрос #}
<p>{{ forms.input('username') }}</p>
<p>{{ forms.input('password', null, 'password') }}</p>

Если макрос определен и используется в одном и том же шаблоне, то для его импорта используется специальная переменная _self.


{% import _self as forms %}
<p>{{ forms.input('username') }}</p>

Если нужно использовать макрос в другом макросе, но в том же самом шаблоне, то макрос импортируется локально внутри другого макроса:


{% macro input(name, value, type, size) %}
    <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}
{% macro wrapped_input(name, value, type, size) %}
    {% import _self as forms %}
    <div class="field">
        {{ forms.input(name, value, type, size) }}
    </div>
{% endmacro %}

В 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 %}
      <ul{{ attributes }}>
    {% else %}
      <ul>
    {% endif %}
    {% for item in items %}
      <li{{ item.attributes }}>
        {{ link(item.title, item.url) }}
        {% if item.below %}
          {{ menus.menu_links(item.below, attributes, menu_level + 1) }}
        {% endif %}
      </li>
    {% endfor %}
    </ul>
  {% endif %}
{% endmacro %}

Тег import

Тег import импортирует все макросы из указанного шаблона:


{# forms.html.twig #}
{# Описываем макрос input() #}
{% macro input(name, value, type, size) %}
    <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}
{# Описываем макрос textarea() #}
{% macro textarea(name, value, rows, cols) %}
    <textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
{% endmacro %}

{# Импортируем в другой шаблон макросы из forms.html.twig #}
{% import 'forms.html' as forms %}
<dl>
    <dt>Username</dt>
    <dd>{{ forms.input('username') }}</dd>
    <dt>Password</dt>
    <dd>{{ forms.input('password', null, 'password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>

Тег from

Тег from импортирует только указанные макросы из шаблона:


{# Импортируем из forms.html.twig макросы: input() с алиасом input_field и textarea() #}
{% from 'forms.html' import input as input_field, textarea %}
<dl>
    <dt>Username</dt>
    <dd>{{ input_field('username') }}</dd>
    <dt>Password</dt>
    <dd>{{ input_field('password', '', 'password') }}</dd>
</dl>
<p>{{ textarea('comment') }}</p>

Тег include

Тег include включает в себя имя шаблона и возвращает отображаемое содержимое этого файла в текущий шаблон. Все переменные текущего шаблона будут доступны в подключаемых шаблонах. С помощью include можно некоторые блоки кода вынести в отдельные шаблоны:


{# header.html.twig #}
<header>{{ header }}</header>

{# footer.html.twig #}
<footer>{{ footer }}</footer>

{# page.html.twig - available variables: header, footer #}
{% include 'header.html' %}
<main>Body</main>
{% include 'footer.html' %}
{# 
Результат:
<header>Header</header>
<main>Body</main>
<footer>Footer</footer>
#}

Можно добавить дополнительные переменные, передавая их после 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 %}
  <div class="node__links">
    {% include "links.html.twig" %}
  </div>
{% endif %}
{# В подключаемом шаблоне links.html.twig доступны все переменные из links--node.html.twig #}

Тег set

Внутри шаблонов можно создавать переменные с помощью тега set:


{# Записываем в переменную строку #}
{% set foo = 'foo' %}
{# Записываем в переменную массив #}
{% set foo = [1, 2] %}
{# Записываем в переменную ассоциативный массив #}
{% set foo = {'foo': 'bar'} %}
{# символ ~ осуществляет конкатенацию строк #}
{% set foo = 'foo' ~ 'bar' %}
{#  Результат: foo содержит 'foobar' #}
{% set foo, bar = 'foo', 'bar' %}
{# Создано две переменных foo и bar #}

С помощью тега set в переменную можно записать блок кода:


{% set foo %}
  <div id="pagination">
    ...
  </div>
{% endset %}

Тег spaceless

Тег spaceless можно использовать для удаления пробелов между HTML-тегами, а также содержимого внутри HTML-тегов или пробелов в простом тексте:


{% spaceless %}
    <div>
        <strong>foo</strong>
    </div>
{% endspaceless %}
{# Результат: <div><strong>foo</strong></div> #}

Тег autoescape

В зависимости от того, включено или нет автоматическое экранирование, вы можете отметить фрагмент шаблона для экранирования или же отключить эту функцию, используя тег autoescape (в Drupal 8 автоэкранирование включено в контексте html):


{% autoescape %}
    Автоэкранирование в этом блоке осуществляется методами HTML.
{% endautoescape %}

{% autoescape 'html' %}
    Автоэкранирование в этом блоке осуществляется методами HTML.
{% endautoescape %}

{% autoescape 'js' %}
    Автоэкранирование в этом блоке осуществляется методами js.
{% endautoescape %}

{% autoescape false %}
    Автоэкранирование отключено. Содержимое блока выводится без изменений.
{% endautoescape %}

Тег verbatim

Тег verbatim маркирует секцию и сообщает шаблонизатору не обрабатывать содержимое блока:


{% verbatim %}
    <ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
{% endverbatim %}
{# Результат:
    <ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
#}

Тег filter

Тег filter позволяет применять стандартные фильтры шаблонизатора Twig для блоков данных шаблонов. Просто разместите код внутри секции "filter":


{% filter upper %}
    Drupal 8
{% endfilter %}
{# Результат: 'DRUPAL 8' #}

Можно также создавать комбинации фильтров:


{% filter lower|escape %}
    <strong>SOME TEXT</strong>
{% endfilter %}
{# Результат: '&lt;strong&gt;some text&lt;/strong&gt;' #}

Если в теге используется фильтры escape или e, то drupal их заменит на свой фильтр drupal_escape.

Тег do

Тег do работает так же, как стандартное выражение с переменной {{ var }}, с той разницей, что ничего не выводится. На практике применим, если нужно вызвать функцию или метод объекта, которые что либо могут возвращать или выводить, но в выводе шаблона эти данные не нужны отображать.


{% do 1 + 2 %}
{% do product.delete() %}

Тег trans

Тег trans специфичен для Drupal 8. Этот тег позволяет переводить текст (используя токены) внутри тега используя функцию t() или TranslationManager::formatPlural(), если внутри тега использована конструкция {% plural ... %}:


<p class="submitted">
{% trans %}Submitted by {{ author.name }} on {{ node.date }}{% endtrans %}
</p>

С фильтром t перевод той же самой строки выглядел бы так:


<p class="submitted">
  {{ "Submitted by @author on @date"|t({ '@author': author.name, '@date': node.date }) }}
</p>

По умолчанию все токены передаются в t() или TranslationManager::formatPlural() с префиксом @ для экранирования. Для использования префикса % нужно модифицировать токен-переменную с помощью фильтра placeholder:


{% set string = '&"<>' %}
<div>
  {% trans %}
    Escaped: {{ string }}
    {# В перевод попадет строка: 'Escaped: @string' #}
  {% endtrans %}
</div>
</div>
<div>
  {% trans %}
    Placeholder: {{ string|placeholder }}
    {# В перевод попадет строка: 'Placeholder: %string' #}
  {% endtrans %}
</div>

Для использования разных форм для множественного числа существительных используйте конструкцию {% plural ... %} внутри тега trans:


{% set count = comments|length %}
{% trans %}
  {# Задается строка для единственного числа #}
  {{ count }} comment was deleted successfully.
{% plural count %}
  {# Задается строка для множественного числа #}
  {{ count }} comments were deleted successfully.
{% endtrans %}
{# 
В результате вывод будет в зависимости от count:
для 1: '1 комментарий был успешно удален.'
для 2: '2 комментария было успешно удалено.'
для 5: '5 комментариев было успешно удалено.'
#}

Для использования перевода для множественных чисел существительных в административном интерфейсе сайта на странице /admin/config/regional/translate нужно указать переводы для единственного числа и для двух форм множественного:

В функции t() и методе TranslationManager::formatPlural() имеется параметр $options, с помощью которого передается дополнительный контекст или язык для перевода (подробнее в документации t()). Для использования этих опций в теге trans, используйте синтаксис with { ... } в открывающемся теге trans:


{% trans with {'context': 'Long month name', 'langcode': 'fr'} %}
  January
{% endtrans %}

Строку для перевода можно также задавать аргументом прямо в открывающемся теге trans:


<div>
  {% trans 'Hello sun.' %}
</div>



Официальная документация по Twig тегам http://twig.sensiolabs.org/doc/tags/index.html