Twig в Symfony: что важно понять сразу
Что вы получите в конце: рабочий шаблон Twig в Symfony, который передаёт данные из контроллера, наследует базовую разметку и не падает на пустых переменных. На всё уйдёт около 30 минут.

Twig — шаблонизатор по умолчанию в Symfony 7.x. Если вы только что установили фреймворк и хотите вывести хоть что-то на страницу, вам нужно понять три вещи: как контроллер передаёт данные в шаблон, как шаблоны наследуют друг друга и почему {{ }} и {% %} — это не одно и то же. Разберёмся по шагам, в конце будет работающая страница с наследованием и передачей переменных.
Что нужно иметь до начала
- PHP 8.2 или новее — этого требует Symfony 7.x;
- Composer, установленный глобально;
- сам Symfony-проект. Если его ещё нет, создайте веб-приложение командой
composer create-project symfony/skeleton my_app, затем в папке проекта поставьте пакет для шаблонов; - пакет
symfony/twig-bundle. При установке черезcomposer require symfony/twig-bundleFlex сам создаст папкуtemplates/и конфигconfig/packages/twig.yaml.
Проверить, что Twig подключён: в папке проекта должна появиться директория templates/. Если её нет — bundle не установился, вернитесь к composer require.
Шаг 1. Три вида скобок — запомните сразу
Больше всего ошибок у новичков именно тут. В Twig есть три конструкции, и они не взаимозаменяемы:
{{ переменная }}— вывести значение на страницу. Здесь идёт то, что видит пользователь.{% тег %}— логика: условия, циклы, наследование. Ничего не выводит само по себе.{# комментарий #}— комментарий, в HTML-исходник страницы не попадает.
Частая ошибка — написать {% user.name %}, ожидая вывод. Так вы получите ошибку синтаксиса: для вывода нужны двойные фигурные скобки.
Шаг 2. Контроллер отдаёт данные в шаблон
Создайте файл src/Controller/PageController.php. Метод render() принимает имя шаблона и массив переменных — ключи массива становятся именами переменных внутри Twig.
<?php
// src/Controller/PageController.php (Symfony 7.x)
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class PageController extends AbstractController
{
#[Route('/hello', name: 'app_hello')]
public function hello(): Response
{
// Ключи массива — это имена переменных в шаблоне
return $this->render('page/hello.html.twig', [
'title' => 'Первая страница',
'user' => ['name' => 'Аня', 'role' => 'admin'],
'items' => ['Молоко', 'Хлеб', 'Кофе'],
]);
}
}
Проверка: пока шаблона нет, страница /hello вернёт ошибку «Unable to find template». Это нормально — создадим его на следующем шаге.
Шаг 3. Базовый шаблон и наследование
Наследование — главное, ради чего используют Twig. Один базовый файл держит разметку страницы, дочерние шаблоны подставляют в него содержимое через блоки. Так вы не копируете <head> в каждый шаблон.
Файл templates/base.html.twig обычно уже создан bundle. Приведите его к такому виду:
{# templates/base.html.twig #}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}Мой сайт{% endblock %}</title>
</head>
<body>
<main>
{% block body %}{% endblock %}
</main>
</body>
</html>
Тег {% block %} объявляет «дыру», которую заполнит дочерний шаблон. Текст внутри блока в базовом файле — значение по умолчанию, если дочерний блок не переопределит его.
Шаг 4. Дочерний шаблон с данными
Создайте templates/page/hello.html.twig. Первая строка — extends, она указывает, от какого шаблона наследоваться. Всё содержимое должно лежать внутри блоков, объявленных в базовом.
{# templates/page/hello.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}{{ title }}{% endblock %}
{% block body %}
<h1>Привет, {{ user.name }}</h1>
{# Условие: проверяем роль #}
{% if user.role == 'admin' %}
<p>У вас права администратора</p>
{% endif %}
{# Цикл по массиву #}
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endblock %}
Обратите внимание: к элементу массива user['name'] и к свойству объекта user.name в Twig обращаются одинаково — через точку. Twig сам решает, массив это, свойство объекта или метод-геттер. Это удобно, но и источник путаницы: одна и та же запись работает по-разному в зависимости от того, что вы передали.
Проверка: откройте /hello в браузере. Должен появиться заголовок «Привет, Аня», строка про администратора и список из трёх пунктов. Если так — наследование и передача данных работают.
Автоэкранирование: почему ваш HTML превратился в текст
Twig по умолчанию экранирует вывод: если в переменной лежит <b>текст</b>, на странице вы увидите буквально <b>текст</b>, а не жирный текст. Это защита от XSS, и отключать её бездумно нельзя.
Автоэкранирование Twig — не помеха, а причина, по которой чужой ввод на вашем сайте не превращается в исполняемый скрипт. Фильтр |raw отключает эту защиту точечно — применяйте его только к тому, чему доверяете на сто процентов.
Если вам действительно нужно вывести готовый HTML (например, из вашей же CMS, а не из пользовательского поля), используйте фильтр |raw:
{{ article.content|raw }}
Полезные фильтры на первое время
Фильтр применяется через вертикальную черту и меняет значение перед выводом. Вот те, что понадобятся почти сразу:
| Фильтр | Что делает | Пример |
|---|---|---|
|upper | Верхний регистр | {{ name|upper }} |
|date | Форматирует дату | {{ post.createdAt|date('d.m.Y') }} |
|default | Значение, если переменная пуста | {{ nick|default('Гость') }} |
|length | Длина строки или массива | {{ items|length }} |
|raw | Отключает экранирование | {{ html|raw }} |
Если не получилось
Самые частые ошибки на старте и что с ними делать:
- «Unable to find template "page/hello.html.twig"» — Twig ищет шаблоны в папке
templates/. Проверьте путь и расширение: файл должен называться именноhello.html.twigи лежать вtemplates/page/. - «Variable "user" does not exist» — вы обращаетесь к переменной, которую контроллер не передал. Сверьте ключи в массиве
render()с именами в шаблоне. По умолчанию отсутствие переменной вызывает ошибку, а не пустую строку. - «Unexpected token ... expected "end of statement block"» — обычно перепутаны скобки: логика написана в
{{ }}или вывод в{% %}. Проверьте, что для вывода стоят двойные фигурные, для тегов — процент. - Изменения в шаблоне не видны в браузере — включён кеш. В dev-окружении Twig обычно перечитывает шаблоны сам, но если правили конфиг, очистите кеш:
php bin/console cache:clear. - HTML выводится как текст — сработало автоэкранирование. Если вывод должен быть разметкой и источнику вы доверяете, добавьте
|raw.
Что делать дальше
Когда базовое наследование заработало, следующий шаг — вынести повторяющиеся куски в отдельные файлы через {% include %} и научиться формировать ссылки через функцию path() по имени маршрута, а не хардкодить URL. Формирование форм в Twig — отдельная большая тема, здесь мы её не трогали. За полным списком тегов, фильтров и функций держите под рукой официальную документацию Twig — она разбита ровно по этим категориям.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Twig — The flexible, fast, and secure template engine (официальная документация), Creating and Using Templates — Symfony Docs
Частые вопросы
А если у меня Symfony 6, а не 7 — этот код сработает?
Почему у меня переменная выводится пустой, хотя данные в контроллере есть?
Обязательно ли расширение .html.twig или можно просто .twig?
Можно ли вызывать методы объекта прямо в шаблоне?
Как безопасно вывести пользовательский HTML, не открыв дыру для XSS?
Кеш Twig в проде — о чём помнить?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.