Twig в Symfony: рабочие приёмы для шаблонов
Логика в шаблоне, тяжёлые запросы во вложенном include, дубли разметки — разбираем, как строить шаблоны Twig в Symfony 7.x так, чтобы они не тормозили и не разваливались при первом рефакторинге.

Шаблон должен собрать готовые данные в HTML — и всё. Но в реальном проекте на Symfony 7.x Twig-файл быстро обрастает вызовами методов сущностей, фильтрацией коллекций и запросами к базе прямо из {% for %}. Страница начинает делать по сорок SQL-запросов, а найти, где именно, невозможно: логика размазана по десятку include. Разбираем, где в шаблоне проходит граница ответственности, чем платит каждый способ переиспользования разметки и как не превратить шаблон в контроллер.
Задача: карточка товара с блоками, которые повторяются
Возьмём типичный случай — страница товара, где повторяются блок цены, блок рейтинга и кнопка «в корзину». Их же надо показать в списке товаров и в блоке «похожие». Очевидное решение — скопировать разметку. Через месяц дизайн цены меняется, и вы правите её в пяти файлах, забыв про шестой.
Второе очевидное решение — вынести всё в один большой include и передавать туда объект товара целиком. Тоже ловушка: внутри такого include легко написать {{ product.reviews|length }}, а если reviews — ленивая коллекция Doctrine, Twig дёрнет отдельный запрос на каждый товар в списке. Тридцать товаров — тридцать запросов, и вы этого не увидите, пока не откроете профайлер.
Три способа переиспользовать разметку
В Twig есть три механизма, и они не взаимозаменяемы. Разница — в изоляции области видимости и в том, кто готовит данные.
- include — вставляет другой шаблон. По умолчанию видит все переменные родителя, что и создаёт скрытые запросы. Изоляцию включают явно.
- embed — тот же include, но с возможностью переопределять блоки внутри вставляемого шаблона. Нужен, когда переиспользуемый кусок имеет «дырки» под контент.
- компонент (пакет
symfonycasts/twig-component) — шаблон со своим PHP-классом, который сам готовит данные. Логика уезжает из Twig в класс.
include с явной изоляцией
Самый частый источник лишних запросов — include, который наследует контекст родителя. Передавайте только то, что нужно, и добавляйте only:
{# Symfony 7.x, Twig 3.x #}
{# price.html.twig ждёт ровно две переменные и ничего больше не видит #}
{{ include('product/_price.html.twig', {
amount: product.price,
currency: 'RUB'
}, with_context = false) }}
Флаг with_context = false (или короткая форма only) отрезает шаблону доступ к родительским переменным. Теперь внутри _price.html.twig нельзя случайно написать {{ product.reviews }} — переменной product там просто нет. Это защита от ленивых запросов на уровне разметки.
Если во include вы передаёте объект сущности целиком «на всякий случай» — вы отдаёте шаблону право делать запросы к базе, о которых не знаете. Передавайте примитивы и заранее посчитанные значения.
embed, когда нужны дырки под контент
Карточка с рамкой, заголовком и произвольным телом — классический случай для embed. В обычный include контент «сверху» не передашь, а embed позволяет переопределить блоки вставляемого шаблона:
{# _card.html.twig #}
{% block head %}{% endblock %}
{% block body %}{% endblock %}
{# использование #}
{% embed 'ui/_card.html.twig' %}
{% block head %}Похожие товары{% endblock %}
{% block body %}
{% for item in similar %}
{{ include('product/_price.html.twig', { amount: item.price, currency: 'RUB' }, only) }}
{% endfor %}
{% endblock %}
{% endembed %}
Компонент, когда есть логика подготовки
Если для блока надо посчитать скидку, отформатировать статус или подтянуть связанные данные — это не работа шаблона. Twig-компонент выносит подготовку в PHP-класс, а шаблон остаётся тонким:
amount / 100, 2, ',', ' ');
}
}
{# templates/components/ProductPrice.html.twig #}
{{ this.formatted }} {{ currency }}
Вызов в шаблоне становится декларативным:
Синтаксис <twig:ComponentName /> появился в Twig Component 2.x. Точную минимальную версию пакета сверьте в его документации — в старых релизах вызов был через функцию component().
Когда что выбирать
| Механизм | Когда брать | Чем платите |
|---|---|---|
| include + only | Разметка без логики, данные готовит родитель | Все переменные перечисляете руками |
| embed | Обёртка с дырками под произвольный контент | Сложнее читать, вложенные блоки путают |
| Twig-компонент | Есть логика подготовки или состояние | Отдельный класс, дополнительный пакет, чуть больше накладных расходов |
Простое правило: пока блоку не нужно ничего вычислять — хватит include с only. Как только в шаблоне появляется if с бизнес-условием или форматирование — переносите в компонент или в контроллер.
Что ломается на практике
Три ошибки, которые встречаются чаще всего.
- Ленивые запросы из шаблона.
{{ product.reviews|length }}в цикле по списку — это N запросов. Считайте количество в репозитории через отдельный запрос сCOUNTи передавайте число, а не коллекцию. - Логика в Twig. Тернарники в тернарниках,
{% set %}с вычислениями, фильтрация коллекций через|filterс замыканием. Всё это не покрывается юнит-тестами и не находится грепом. Переносите в контроллер или компонент. - include без only. Работает, пока родитель случайно содержит переменную с нужным именем. При рефакторинге родителя include молча получает другое значение или
null.
Как найти лишние запросы
Symfony Profiler в dev-режиме показывает вкладку Doctrine с числом запросов на страницу и вкладку Twig с деревом отрендеренных шаблонов. Если число запросов растёт линейно с числом элементов в списке — ищите обращение к ленивой связи внутри цикла. Чините на уровне запроса в репозитории (JOIN или отдельный агрегат), а не в шаблоне.
Что делать дальше
Пройдите по шаблонам грепом на include( без only и добавьте изоляцию везде, где передаёте объекты. Откройте профайлер на самых тяжёлых страницах и посмотрите число SQL-запросов — всё, что растёт с длиной списка, лечится на уровне репозитория. Для блоков с логикой подключите symfonycasts/twig-component и переносите подготовку данных в классы. Автоэкранирование включено по умолчанию — не отключайте его глобально; если нужен сырой HTML, используйте |raw точечно и только для проверенных данных.
Разрушитель авто-разводов: Фото заказ-наряда СТО: завышения, дубли работ, лишние позиции.
Спросить за 15 ₽Источники: Twig — официальная документация (include, embed, escaping), Symfony UX Twig Components
Частые вопросы
У меня не работает флаг only, Twig ругается на синтаксис
Чем include отличается от embed простыми словами
Как понять, что шаблон делает лишние запросы к базе
Можно ли класть бизнес-логику в Twig, если так быстрее
Стоит ли всегда добавлять only к include
Отключать ли автоэкранирование ради вставки готового HTML
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.