Создание и использование шаблонов

Дата обновления перевода 2023-01-11

Создание и использование шаблонов

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

Язык шаблонов Twig

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1>{{ page_title }}</h1>

        {% if user.isLoggedIn %}
            Hello {{ user.name }}!
        {% endif %}

        {# ... #}
    </body>
</html>

Синтаксис Twig основывается на следующих трех конструкциях:

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

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

1
{{ title|upper }}

Twig поставляется с длинным списком тегов, фильтров и функций, которые доступны по умолчанию. В приложениях Symfony вы можете также использовать эти фильтры и функции Twig, определенные Symfony и вы можете создавать собственные функции и фильтры Twig.

Twig быстр в окружении prod (так как шаблоны компилируются в PHP и кешируются автоматически), но удобнее использовать окружение dev (потому что шаблоны повторно компилируются автоматически при их изменении).

Конфигурация Twig

Twig имеет несколько опций конфигурации для определения вещея вроде формата, используемого для отображения цифр и дат, кеширования шаблонов и т.д. Прочтите справочник конфигурации Twig, чтобы узнать о них больше.

Создание шаблонов

Перед детальным разъяснение того, как создавать и отображать шаблоны, посмотрите на следующий пример для быстрого понимания всего процесса. Для начала, вам нужно создать новый файл в каталоге templates/, чтобы хранить содержание шаблона:

1
2
3
{# templates/user/notifications.html.twig #}
<h1>Hello {{ user_first_name }}!</h1>
<p>У вас {{ notifications|length }} новых уведомлений.</p>

Затем, создайте контроллер, отображающий этот шаблон и передающий ему необходимые переменные:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Controller/UserController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class UserController extends AbstractController
{
    // ...

    public function notifications(): Response
    {
        // получить информацию пользователя и уведомления каким-то образом
        $userFirstName = '...';
        $userNotifications = ['...', '...'];

        // путь шаблона - это относительн путь файла из `templates/`
        return $this->render('user/notifications.html.twig', [
            // этот массив определяет переменные, переданные шаблону, где ключ - это
            // имя переменной, а значение - значение переменной
            // (Twig рекомендует использование имен переменных snake_case : 'foo_bar' вместо 'fooBar')
            'user_first_name' => $userFirstName,
            'notifications' => $userNotifications,
        ]);
    }
}

Именование шаблонов

Symfony рекомендует следующие имена шаблонов:

  • Используйте snake case для имен файлов и каталогов (например, blog_posts.html.twig, admin/default_theme/blog/index.html.twig, и т.д.);
  • Определите два расширения для имен файлов (например, index.html.twig или blog_posts.xml.twig) в виде первого расширения (html, xml, и т.д.), финальный формат которого будет генерировать шаблон.

Хотя шаблоны обычно генерируют содержание HTML, они могут генерировать любой текстовый формат. Поэтому соглашение о двух расширениях упрощает то, как шаблоны создаются и отображается в нескольких форматах.

Местоположение шаблонов

Шаблоны хранятся по умолчанию в каталоге templates/. Когда сервис или контроллер отображает шаблон product/index.html.twig, они на самом деле ссылаются на файл <your-project>/templates/product/index.html.twig.

Каталог шаблонов по умолчанию можно сконфигурировать с помощью опции twig.default_path и вы можете добавить больше каталогов шаблонов как объясняется позже в этой статье.

Переменные шаблонов

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

1
<p>{{ user.name }} добавил комментарий в {{ comment.publishedAt|date }}</p>

Нотация user.name означает, что вы хотите отобразить некоторую информацию (name), хранящуюся в переменной (user). user - это массив или объект? name - это свойство или метод? В Twig это не имеет значения.

при использованиия нотации foo.bar, Twig пытается получить значение переменной в следующем порядке:

  1. $foo['bar'] (массив и элемент);
  2. $foo->bar (объект и публичное свойство);
  3. $foo->bar() (объект и публичный метод);
  4. $foo->getBar() (объект и метод getter);
  5. $foo->isBar() (объект и метод isser);
  6. $foo->hasBar() (объект и метод hasser);
  7. Если ничего из вышеперечисленного не существует, используйте null (или вызовите исключение, если включена опция strict_variables).

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

Ссылание на страницы

Вместо написания URL ссылок от руки, используйте функцию path(), чтобы сгенерировать URL, основываясь на конфигурации маршрутизации .

Позже, если вы захотите изменить URL конкретной страницы, все, что вам нужно будет сделать - это изменить конфигурацию маршрутизации: шаблоны автоматически сгенерируют новый URL.

Рассмотрите следующую конфигурацию маршрутизации:

  • Attributes
  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/BlogController.php
namespace App\Controller;

// ...
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends AbstractController
{
    #[Route('/', name: 'blog_index')]
    public function index(): Response
    {
        // ...
    }

    #[Route('/article/{slug}', name: 'blog_post')]
    public function show(string $slug): Response
    {
        // ...
    }
}

Используйте функцию Twig path(), чтобы сослаться на эти страницы и передать имя маршрута в качестве первого аргумента, а параметры маршрута в качестве необязательного второго аргумента:

1
2
3
4
5
6
7
8
9
10
11
<a href="{{ path('blog_index') }}">Homepage</a>

{# ... #}

{% for post in blog_posts %}
    <h1>
        <a href="{{ path('blog_post', {slug: post.slug}) }}">{{ post.title }}</a>
    </h1>

    <p>{{ post.excerpt }}</p>
{% endfor %}

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

1
2
3
4
5
6
7
8
9
10
11
<a href="{{ path('blog_index') }}">Homepage</a>

{# ... #}

{% for post in blog_posts %}
    <h1>
        <a href="{{ path('blog_post', {slug: post.slug}) }}">{{ post.title }}</a>
    </h1>

    <p>{{ post.excerpt }}</p>
{% endfor %}

Функция path() генерирует относительные URL. Если вам нужно сгенерировать абсолютные URL (например, при отображении шаблонов для электронной почты или лент RSS), используйте функцию url(), которая берет те же аргументы, что и path() (например, <a href="{{ url('blog_index') }}"> ... </a>).

Ссылание на ресурсы CSS, JavaScript и изображений

Если шаболну нужно сослаться на статический ресурс (например, изображение), Symfony предоставляет функцию Twig asset(), чтобы помочь сгенерировать этот URL. Для начала, установите пакет asset:

1
$ composer require symfony/asset

Теперь вы можете использовать функцию asset():

1
2
3
4
5
6
7
8
{# изображение живет тут "public/images/logo.png" #}
<img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>

{# CSS-файл живет тут "public/css/blog.css" #}
<link href="{{ asset('css/blog.css') }}" rel="stylesheet"/>

{# JS-файл живет тут "public/bundles/acme/js/loader.js" #}
<script src="{{ asset('bundles/acme/js/loader.js') }}"></script>

Главной целью функции asset() является сделать ваше приложение более портативным. Если ваше приложение живет в корне вашего хостинга (например, https://example.com), то отображенный путь должен быть /images/logo.png. Но если ваше приложение живет в подкаталоге (например, https://example.com/my_app), каждый путь ресурса должен отображаться с подкаталогом (например, /my_app/images/logo.png). Функция asset() заботится об этом, определяя то, как ваше приложение используется, и соответственно генерируя правильные пути.

Tip

Функция asset() поддерживает разные техники усиления кеша через опции конфигурации version , version_format , и json_manifest_path .

Tip

Если вам нужна помощь с упаковкой, версионирование и минимизирование ваших ресурсов JavaScript и CSS в соверменном виде, прочтите о Symfony Webpack Encore.

Если вам для ресурсов нужны абсолютные URL, используйте функцию Twwig absolute_url() следующим образом:

1
2
3
<img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!"/>

<link rel="shortcut icon" href="{{ absolute_url('favicon.png') }}">

Построение, версионирование и более подвинутая обработка CSS, JavaScript и изображений

Чтобы получить помощь в посторении, версионировании и уменьшении ваших ресурсов JavaScript и CSS современным способом, прочтите про Symfony Webpack Encore.

Глобальная переменная для всего приложения

Symfony создает объект контекста, который автоматически внедряется в каждый шаблон Twig в виде переменной под названием app. Она предоставляет доступ к некоторой информации приложения:

1
2
3
4
5
<p>Username: {{ app.user.username ?? 'Anonymous user' }}</p>
{% if app.debug %}
    <p>Request method: {{ app.request.method }}</p>
    <p>Application Environment: {{ app.environment }}</p>
{% endif %}

Перемення app (которая является экземпляром AppVariable) предоставляет вам доступ к таким переменным:

app.user
Текущий объект пользователя или null, если пользователь не аутентифицирован.
app.request
Объект Request, который хранит текущие данные запроса data (в зависимости от вашего приложения, это может быть подзапросом или обычным запросом).
app.session
Объект Session, который представляет текущую сессию мользователя или null, если ее нет.
app.flashes
Массив всех флэш-сообщений , хранящихся в сессии. Вы можете также получить только сообщения определенного типа (например, app.flashes('notice')).
app.environment
Имя текущего окружения конфигурации (dev, prod, и т.д.).
app.debug
True, если в режим отладки . False - в других режимах.
app.token
Объект TokenInterface, представляющий токен безопасности.
app.current_route
Имя маршрута, ассоциированного с текущим запросом или null, если запрос не доступен (эквивалентно app.request.attributes.get('_route'))
app.current_route_parameters
Массив с параметрами, переданный маршруту текущего запроса или пустой массив, если запрос не доступен (эквивалентно app.request.attributes.get('_route_params'))

6.2

Переменные app.current_route и app.current_route_parameters были представлены в Symfony 6.2.

В дополнение к глобальной переменной app, внедренной Symfony, вы также можете автоматически внедрять переменные во все шаблоны Twig.

Компоненты Twig

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

Чтобы узнать больше, см. Компонент UX Twig.

Компоненты Twig также имеют еще одну суперсилу: они становятся "живыми", когда автоматически обновляются (через Ajax) по мере взаимодействия пользователя с ними. Например, когда ваш пользователь вводит текст в поле, ваш компонент Twig повторно отобразится через Ajax, чтобы показать список результатов!

Чтобы узнать больше, см. Компонент UX Live.

Отображение шаблонов

Отображение шаблона в контроллерах

Если ваш контроллер расширяется из AbstractController , используйте помощник render():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/Controller/ProductController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class ProductController extends AbstractController
{
    public function index(): Response
    {
        // ...

        // метод `render()` возвращает объект `Response` с
        // содержанием, созданным шаблоном
        return $this->render('product/index.html.twig', [
            'category' => '...',
            'promotions' => ['...', '...'],
        ]);

        // метод `renderView()` возвращает только содержание, созданное
        // шаблоном, поэтому вы можете использовать это содержание позже в объекте `Response`
        $contents = $this->renderView('product/index.html.twig', [
            'category' => '...',
            'promotions' => ['...', '...'],
        ]);

        return new Response($contents);
    }
}

Если ваш контроллер не расширяется из AbstractController, вам понадобится извлечь сервисы в вашем контроллере и использовать метод render() сервиса twig.

Еще одна опция - использовать атрибут #[Template()] в методе контроллера, чтобы определить шаблон для отображения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Controller/ProductController.php
namespace App\Controller;

use Symfony\Bridge\Twig\Attribute\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class ProductController extends AbstractController
{
    #[Template('product/index.html.twig')]
    public function index()
    {
        // ...

        // при использовании атрибута #[Template()], вам нужно только вернуть массив
        // с параметрами для передачи шаблону (атрибут сам создаст и вернет объект
        // Response).
        return [
            'category' => '...',
            'promotions' => ['...', '...'],
        ];
    }
}

6.2

Атрибут #[Template()] был представлен в Symfony 6.2.

Отображение шаблона в сервисах

Внедрите сервис Symfony twig в ваши собственные сервисы и используйте его метод render(). При использовании автомонтирования сервисов, вам понадобится только добавить аргумент в конструктор сервиса и добавить подсказку к классу Environment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Service/SomeService.php
namespace App\Service;

use Twig\Environment;

class SomeService
{
    private $twig;

    public function __construct(Environment $twig)
    {
        $this->twig = $twig;
    }

    public function someMethod()
    {
        // ...

        $htmlContents = $this->twig->render('product/index.html.twig', [
            'category' => '...',
            'promotions' => ['...', '...'],
        ]);
    }
}

Отображение шаблона в электронных письмах

Прочтите документы об интеграции Mailer и Twig .

Отображение шаблона прямо из маршрута

Хотя шаблоны обычно отображаются в контроллерах и сервисах, вы можете отобразить статические страницы, не требующие переменных, прямо из определения маршрута. Используйте специальный TemplateController, предоставленный Symfony:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# config/routes.yaml
acme_privacy:
    path:          /privacy
    controller:    Symfony\Bundle\FrameworkBundle\Controller\TemplateController
    defaults:
        # путь шаблона для отображения
        template:  'static/privacy.html.twig'

        # статус-код ответа (по умолчанию: 200)
        statusCode: 200

        # специальные опции, определенные Symfony для установки кеша страницы
        maxAge:    86400
        sharedAge: 86400

        # должно ли кеширование применяться только к кешам клиентов
        private: true

        # по желанию вы можете определеить некоторые аргументы, переданные шаблону
        context:
            site_name: 'ACME'
            theme: 'dark'

Проверка существования шаблона

Шаблоны загружаются в приложение с использованием загрузчика шаблонов Twig, который также предоставлят метод для проверки существования шаблонов. Для начала, получите загрузчик:

1
2
3
4
5
6
7
8
9
10
11
use Twig\Environment;

class YourService
{
    // этот код предполагает, что ваш сервис использует автомонтирование для внедрения зависимостей
    // в другом случае, вручную внедрите сервис под названием 'twig'
    public function __construct(Environment $twig)
    {
        $loader = $twig->getLoader();
    }
}

Затем, передайте путь шаблона Twig template методу загрузчика exists():

1
2
3
4
if ($loader->exists('theme/layout_responsive.html.twig')) {
    // шаблон существует, сделайте что-то
    // ...
}

Отладка шаблонов

Symfony предоставляет несколько утилит для помощи с отладкой проблем в ваших шаблонах.

Проверка соблюдения стандартов кодирования шаблонов Twig

Команда lint:twig проверяет, чтобы в ваших шаблонах Twig не было никаких синтаксических ошибок. Полезно выполнять ее до запуска вашего приложения в производство (например, на вашем сервере непрерывной интеграции):

1
2
3
4
5
6
7
8
9
# проверить все шаблоны приложения
$ php bin/console lint:twig

# вы такжже можете проверить каталоги и отдельные шаблоны
$ php bin/console lint:twig templates/email/
$ php bin/console lint:twig templates/article/recent_list.html.twig

# вы также можете увидеть устаревший функции, используемые в ваших шаблонах
$ php bin/console lint:twig --show-deprecations templates/email/

При запуске линтера внутри действий GitHub, вывод автоматически адаптируется к формату, требуемому GitHub, но вы можете также форсировать этот формат:

1
$ php bin/console lint:twig --format=github

Исследование информации Twig

Команда debug:twig перечисляет всю доступную информацию о Twig (функции, фильтры, глобальные переменные и т.д.). Она полезна для проверки того, правильно ли работают ваши пользовательские расширения Twig, и проверки функций Twig, добавленных при установке пакетов :

1
2
3
4
5
6
7
8
# перечислить общую информацию
$ php bin/console debug:twig

# отфильтровать вывод по любому ключевому слову
$ php bin/console debug:twig --filter=date

# передать путь шаблона, чтобы указать физический файл, который будет загружен
$ php bin/console debug:twig @Twig/Exception/error.html.twig

Утилиты сброса Twig

Symfony предоставляет функцию dump() в качестве улучшенной альтернативы PHP-функции var_dump(). Эта функция полезна для исследования содержания любой переменной, и вы можете использовать ее и в шаблонах Twig.

Для начала, убедитесь, что компонент VarDumper установлен в приложении:

1
$ composer require symfony/var-dumper

Затем, используйте либо тег {% dump %}, либо функцию {{ dump() }}, в зависимости от ваших потребностей:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{# templates/article/recent_list.html.twig #}
{# содержание этой переменной отправляется Панели инструментов веб-отладки, а
   не сбрасывается в содержание страницы #}
{% dump articles %}

{% for article in articles %}
    {# содержание этой переменной сбрасывается в содержание страницы,
       и видимо на веб-странице #}
    {{ dump(article) }}

    <a href="/article/{{ article.slug }}">
        {{ article.title }}
    </a>
{% endfor %}

Чтобы избежать утечки конфиденциальной информации, функция/тег dump() доступна только в окружениях конфигурации dev и test. Если вы попоробуете использовать ее в окружении prod, то увидите PHP-ошибку.

Повторное использование содержания шаблонов

Добавление шаблонов

Если определенный код Twig повторяется в нескольких шаблонах, вы можете извлечь его в один "фрагмент шаблона" и добавить его в другие шаблоны. Представьте, что следующий код для отобржаения информации пользователя повторяется в нескольких местах:

1
2
3
4
5
6
7
{# templates/blog/index.html.twig #}

{# ... #}
<div class="user-profile">
    <img src="{{ user.profileImageUrl }}" alt="{{ user.fullName }}"/>
    <p>{{ user.fullName }} - {{ user.email }}</p>
</div>

Для начала, создайте новый шаблон Twig под названием blog/_user_profile.html.twig (префикс _ не обязателен, но это соглашение, используемое для лучшей дифференциации между полными шаблонами и их фрагментами).

Затем, удалите это содержание из первоначального шаблона blog/index.html.twig, и добавьте следующее, чтобы добавить фрагмент шаблона:

1
2
3
4
{# templates/blog/index.html.twig #}

{# ... #}
{{ include('blog/_user_profile.html.twig') }}

Функция Twig include() берет аргумент пути шаблона, чтобы добавить его. Добавленный шаблон имеет доступ ко всем переменным шаблона, который его содержит (используйте опцию with_context, чтобы контролировать это).

Вы также можете передат переменные добавленному шаблону. Это полезно, к примеру, для переименовывания переменных. Представьте, что ваш шаблон хранит информацию пользователя в переменной под названием blog_post.author, а не переменной user, которую ожидает фрагмент шаблона. Используйте следующее, чтобы переименовать переменную:

1
2
3
4
{# templates/blog/index.html.twig #}

{# ... #}
{{ include('blog/_user_profile.html.twig', {user: blog_post.author}) }}

Встраивание контроллеров

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

Представьте, что фрагмент шаблона отображает три наиболее свежих статьи блога. Чтобы сделать это, ему нужно сделать запрос к базе данных, чтобы получить эти статьи. При использовании функции include(), вам понадобится делать один и тот же запрос к базе данных на каждой странице с этим фрагментом. Это не очень удобно.

Лучшей альтернативой будет встроить результат выполнения некоторого контроллера с функциями Twig render() и controller().

Для начала, создайте контроллер, отображающий определенное число недавних статей:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
// ...

class BlogController extends AbstractController
{
    public function recentArticles(int $max = 3): Response
    {
        // как-то получить наиболее свежие статьи (например, сделать запрос к базе данных)
        $articles = ['...', '...', '...'];

        return $this->render('blog/_recent_articles.html.twig', [
            'articles' => $articles
        ]);
    }
}

Затем, создайте фрагмент шаблона blog/_recent_articles.html.twig (префикс _ в названии шаблона не обязателен, но это соглашение, используемое для лучшей дифференциации между полными шаблонами и их фрагментами):

1
2
3
4
5
6
{# templates/blog/_recent_articles.html.twig #}
{% for article in articles %}
    <a href="{{ path('blog_show', {slug: article.slug}) }}">
        {{ article.title }}
    </a>
{% endfor %}

Теперь вы можете вызвать этот контроллер из любого шаблона, чтобы встроить его результат:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{# templates/base.html.twig #}

{# ... #}
<div id="sidebar">
    {# если контроллер ассоциирован с маршрутом, используйте функции path() или url() #}
    {{ render(path('latest_articles', {max: 3})) }}
    {{ render(url('latest_articles', {max: 3})) }}

    {# если вы не хотите обнажать контроллер публичным URL, используйте
       функцию controller(), чтобы определить контроллер для выполнения #}
    {{ render(controller(
        'App\\Controller\\BlogController::recentArticles', {max: 3}
    )) }}
</div>

При исползовании функции controller(), контроллеры не доступны с использованием обычного маршрута Symfony, но доступны через специальный URL, используемый исключительно для обслуживания этих фрагментов шаблона. Сконфигурируйте этот специальный URL в опции fragments:

  • YAML
  • XML
  • PHP
1
2
3
4
# config/packages/framework.yaml
framework:
    # ...
    fragments: { path: /_fragment }

Caution

Встраивание контроллеров требует отправки запросов этим контроллерам и отображения некоторых шаблонов в качестве результата. Это может иметь значительное влияние на производительность приложения, если вы встраиваете множество контроллеров. Если возможно, кешируйте фрагмент шаблона.

See also

Шаблоны также могут встраивать содержание асинхронно с помощью библиотеки JavaScript hinclude.js.

Наследование шаблонов и макеты

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

Концепт наследования шаблонов Twig похож на наследование PHP-классов. Вы определяете родительский шаблон, из которого могут расширяться другие шаблоны, а дочерние шаблоны могут переопределять части родительского.

Symfony рекомендует следующее трехуровневое наследование шаблонов для средних и сложных приложений:

  • templates/base.html.twig, определяет общие элементы всех шаблонов приложения, такие как <head>, <header>, <footer>, и т.д..;
  • templates/layout.html.twig, расширяется из base.html.twig и определяет структуру содержания, используемую в (практически) всех страницах, такую как содержание в двух колонках + макет боковой панели. Некоторые разделы приложения могут определять свои собственные макеты (например, templates/blog/layout.html.twig);
  • templates/*.html.twig, страницы приложения, расширяющикся из главного шаблона layout.html.twig или любого другого макета раздела.

На практике, шаблон base.html.twig будет выглядеть так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}My Application{% endblock %}</title>
        {% block stylesheets %}
            <link rel="stylesheet" type="text/css" href="/css/base.css"/>
        {% endblock %}
    </head>
    <body>
        {% block body %}
            <div id="sidebar">
                {% block sidebar %}
                    <ul>
                        <li><a href="{{ path('homepage') }}">Home</a></li>
                        <li><a href="{{ path('blog_index') }}">Blog</a></li>
                    </ul>
                {% endblock %}
            </div>

            <div id="content">
                {% block content %}{% endblock %}
            </div>
        {% endblock %}
    </body>
</html>

Тег блоков Twig определяет разделы страницы, которые можно переопределить в дочерних шаболнах. Они могут быть пустыми, как блок content, или определять содержание по умолчанию, как блок title, который отображается, когда дочерний шаблон их не переопределяет.

Шаблон blog/layout.html.twig может быть таким:

1
2
3
4
5
6
7
8
{# templates/blog/layout.html.twig #}
{% extends 'base.html.twig' %}

{% block content %}
    <h1>Blog</h1>

    {% block page_contents %}{% endblock %}
{% endblock %}

Шаблон расширяется из base.html.twig и определяет только содержание блока content. Остальные блоки родительского шаблона будут отображать свое содержание по умолчанию. Однако, он могут быть переопределены наследованием шаблонов третьего уровня, таким как blog/index.html.twig, который отображает индекс блога:

1
2
3
4
5
6
7
8
9
10
11
{# templates/blog/index.html.twig #}
{% extends 'blog/layout.html.twig' %}

{% block title %}Blog Index{% endblock %}

{% block page_contents %}
    {% for article in articles %}
        <h2>{{ article.title }}</h2>
        <p>{{ article.body }}</p>
    {% endfor %}
{% endblock %}

Этот шаблон расширяется из шаблона второго уровня (blog/layout.html.twig), но переопределяет блоки разных родительских шаблонов: page_contents из blog/layout.html.twig и title из base.html.twig.

Когда вы отображаете шаблон blog/index.html.twig, Symfony использует три разных шаблона для создания финального содержания. Этот механизм наследования усиливает вашу продуктивность, потому что каждый шаблон содержит только свое уникальное содержание, и оставляет повторяющееся содержание и HTML-структуру каким-то родительским шаблонам.

Caution

При использовании extends, дочернему шаблону запрещено определять части шаблона вне блока. Следующий код вызывает ошибку SyntaxError:

1
2
3
4
5
6
7
8
{# app/Resources/views/blog/index.html.twig #}
{% extends 'base.html.twig' %}

{# строка ниже не зафиксирована тегом "block" #}
<div class="alert">Some Alert</div>

{# следующее валидно #}
{% block content %}My cool blog posts{% endblock %}

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

Экранирование вывода

Предтавьте, что ваш шаблон включает в себя код Hello {{ name }} для отображения имени пользователя. Если зловредный пользователь установит <script>alert('hello!')</script> в качестве своего имени, и вы выведете это значение без изменений, приложение отобразит всплывающее окно JavaScript.

Это известно как атака межсайтового скриптинга (XSS). И хотя предыдущий пример выглядит безобидным, нападчик может написать более продвинутый код JavaScript, чтобы выполнить зловредные действия.

Чтобы предотвратить эту атаку, используйте "экранирование вывода", чтобы преобразовать символы, имеющие особое значение (например, замените < на HTML-сущность &lt;). Приложения Symfony безопасны по умолчанию, так как они выполняют автоматическое экранирование вывода:

1
2
3
<p>Hello {{ name }}</p>
{# если 'name' - '<script>alert('hello!')</script>', Twig выведет это:
   '<p>Hello &lt;script&gt;alert(&#39;hello!&#39;)&lt;/script&gt;</p>' #}

Если вы отображаете переменную, которой можно доверять, и которая имеет HTML-содержание, используйте фильтр Twig raw, чтобы отключить экранирование вывода для этой переменной:

1
2
3
<h1>{{ product.title|raw }}</h1>
{# если 'product.title' - 'Lorem <strong>Ipsum</strong>', Twig выведет
   именно это вместо 'Lorem &lt;strong&gt;Ipsum&lt;/strong&gt;' #}

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

Пространства имен шаблонов

Хотя большинство приложений хранят свои щаблоны в каталоге по умолчанию templates/, вам может понадобиться хранить некоторые (или все) из них в других каталогах. Используйте опцию twig.paths, чтобы сконфигурирвать эти дополнительные каталоги. Каждый путь определяется как пара key: value, где key - это каталог шаблона, а value - пространство имен Twig, что объясняется позже:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
# config/packages/twig.yaml
twig:
    # ...
    paths:
        # каталоги релятивны к корневому каталогу проекта (но вы также
        # можете использовать абсолютные каталоги)
        'email/default/templates': ~
        'backend/templates': ~

При отображении шаблона, Symfony вначале ищет его в каталогах twig.paths, которые не определяют пространство имен, а затем откатывается до каталога шаблонов по умолчанию (обычно, templates/).

Используя конфигурацию выше, если ваше приложение отображает, к примеру, шаблон layout.html.twig, Symfony вначале будет искать в email/default/templates/layout.html.twig и backend/templates/layout.html.twig. Если любой из этих шаблонов существует, Symfony использует его вместо использования templates/layout.html.twig, что скорее всего является тем шаблоном, который вы хотели использовать.

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

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
# config/packages/twig.yaml
twig:
    # ...
    paths:
        'email/default/templates': 'email'
        'backend/templates': 'admin'

Теперь, если вы отобразите шаблон layout.html.twig, Symfony будет отображать файл templates/layout.html.twig. Используйте специальный синтаксиси @ + пространство имен, чтобы ссылаться на другие шаблоны с пространствами имен (например, @email/layout.html.twig и @admin/layout.html.twig).

Note

Одно пространство имен Twig может ассоциироваться более, чем с одним каталогом шаблонов. В таком случае, порядок, в котором добавляются пути, важен, так как Twig начнет искать шаблоны с первого определенного пути.

Шаблоны пакетов

Если вы устанавливаете пакеты в своем приложении, он могут содержать собственные шаблоны Twig (в каталоге Resources/views/ каждого пакета). Чтобы избежать неразберихи с вашими собственными шаблонами, Symfony добавляет шаблоны пакетов под автоматическим пространством имен, созданным по имени пакета.

Например, шаблоны пакета под названием AcmeFooBundle доступны под пространством имен AcmeFoo. Если этот пакет включает в себя шаблон <your-project>/vendor/acmefoo-bundle/Resources/views/user/profile.html.twig, вы можете сослаться на него так @AcmeFoo/user/profile.html.twig.

Tip

Вы также можете переопределить шаблоны пакетов в случае, если вы хотите изменить некоторые части первоначальных шаблонов пакета.