Twig: шаблоны PHP, страницы и письма — мастерская и 9 примеров
Собираем страницы и письма с Twig: девять практических задач, настоящий PHP, JSON-редактор, HTML-предпросмотр, макросы, экранирование и кеш. Исходники для Windows и Linux/VPS.

Twig без полного фреймворка: шаблоны страниц и писем, наследование, макросы, экранирование и кеш. Настоящая PHP-мастерская, девять практических задач и исходники для Windows и Linux/VPS.
Twig: из данных — в страницу, карточку или письмо
В небольшом PHP-проекте HTML часто начинается со строки: открыли тег, добавили имя, склеили несколько фрагментов и отправили ответ. Пока страница одна, это удобно. Затем появляются список заявок, карточка клиента, печатная форма и письмо. Одни и те же подписи, поля и проверки повторяются, а изменение внешнего вида требует разбираться с условиями внутри PHP.
Twig предлагает вынести разметку в отдельные шаблоны. PHP подготавливает данные, выбирает известный шаблон и получает готовую строку HTML. В шаблоне остаются понятные конструкции: вывести значение, пройти по списку, показать пустое состояние, подключить карточку или заменить блок общего layout. Этот подход пригодится и без Symfony Framework — достаточно PHP и Composer.
В статье мы соберём девять законченных сценариев, а не просто перечислим синтаксис. Вы сможете изменить JSON, отправить его настоящему PHP-обработчику, посмотреть интерфейс и скачать HTML вместе с рецептом запуска. В исходниках есть сами шаблоны, CLI-команды, проверки и путь от локального Windows до PHP-FPM на Linux/VPS.
Где применять: кабинеты, админки, отчёты и уведомления
В личном кабинете Twig удобно собирать страницу из нескольких повторяемых частей: шапки, меню, карточки заказа и сообщения об ошибке. В админке особенно полезны списки и формы: один макрос может сохранить одинаковую связь label с полем, оформление подсказок и расположение ошибки. Посетитель получает последовательный интерфейс, а разработчик меняет разметку в известном месте.
В серверных уведомлениях тот же инструмент готовит HTML-письмо и текстовую альтернативу. Заказ, позиции и суммы передаются как данные; шаблон решает, как их показать. Отправка остаётся отдельной задачей почтового транспорта. Для отчёта можно сформировать HTML, затем передать его инструменту печати или PDF, если такой шаг нужен проекту.
Twig также подходит для обычных страниц на PHP, когда сборка большого JavaScript-приложения избыточна. При этом шаблонизатор не управляет сессией, не получает записи из базы и не проверяет, имеет ли пользователь право видеть заказ. Эти решения принимает приложение до render. Чёткое разделение помогает повторно использовать шаблон в HTTP-контроллере, очереди и консольной команде.
Twig и обычный PHP-шаблон: что меняется
HTML с небольшими вставками PHP остаётся нормальным решением. Например, отдельный файл view.php может получать подготовленный массив и использовать htmlspecialchars при выводе. Twig добавляет единый язык шаблонов, автоматическое экранирование в выбранном контексте, наследование, include, макросы и инструменты диагностики. Выбор зависит от размера проекта и команды.
Если у вас одна статическая страница с двумя переменными, новая зависимость может не дать заметной пользы. Если несколько страниц используют общий каркас, а письма и карточки меняются регулярно, шаблоны становятся отдельным понятным слоем. Не требуется переводить весь проект сразу: можно начать с одного уведомления или страницы отчёта и проверить, стало ли сопровождение проще.
Важно не перенести в Twig весь контроллер. Запрос к базе, вычисление скидки, проверка прав и изменение статуса заказа должны оставаться в PHP-сервисе. В шаблон передаётся уже подготовленное представление данных. Тогда один и тот же шаблон можно проверить без подключения к рабочей базе, подставив небольшой JSON из fixtures.
$data = ['customer' => ['name' => 'Анна', 'active' => true]];
$html = $twig->render('card.html.twig', $data);
// Дальше HTTP-ответ, сохранение отчёта или отдельная отправка письма.
Мини-мастерская: девять задач с настоящим PHP
Выберите практический пример, прочитайте короткую задачу и измените JSON. Кнопка «Выполнить PHP» отправляет данные обработчику сайта: Twig загружает один из фиксированных шаблонов и возвращает фактически сформированный HTML. Предпросмотр показывает результат, а блок ниже — полный ответ и исходную разметку. Шаблоны можно читать прямо в интерфейсе и скачать вместе с проектом.
Начните с карточки: замените имя, выставьте active в false и уберите company. Затем откройте список и замените tasks на пустой массив. В строгом примере специально отсутствует номер договора: добавьте customer.contractNumber и повторите запуск. Эти действия позволяют увидеть связь между входными данными, настройками и полученным интерфейсом.
JSON отправляется на сервер, поэтому используйте учебные значения. Обработчик не сохраняет содержимое в базе или файлах. Произвольные Twig-шаблоны и PHP-код через HTTP не принимаются. Если runtime временно недоступен, отдельная кнопка показывает запись исходного примера; она заметно подписана и не выдаётся за результат ваших изменений. Скачать PHP-рецепт можно только после настоящего успешного выполнения.
Версии и установка через Composer
В скачиваемом проекте закреплён Twig 3.30.0. Это стабильная ветка Twig 3, для которой требуется PHP 8.1 или новее. Мастерская использует PHP CLI/FPM, Composer и локальный Bootstrap 5.3.8. Требования и способ установки описаны в официальном введении Twig; актуальные пакеты доступны на Packagist.
Для нового проекта зависимость можно добавить командой ниже. Для нашего готового архива повторный require не нужен: composer.json и composer.lock уже входят в комплект. Выполните install, чтобы получить именно согласованный набор версий, затем проверьте платформенные требования. Не обходите их флагом ignore-platform-reqs: отсутствие подходящего PHP от этого не исчезнет.
Если Composer пока незнаком, сначала откройте нашу статью Composer: команды, автозагрузка и практические примеры. Там отдельно разобраны lock-файл, установка зависимостей и разница между install и update. В этом материале Composer помогает подключить Twig, а не заменяет настройку PHP-сервера.
composer require twig/twig:3.30.0
composer check-platform-reqs
Windows: первый запуск без отдельного веб-сервера
Распакуйте проект в обычную рабочую папку и откройте в ней PowerShell. Убедитесь, что php и composer доступны в текущем терминале. После установки зависимостей выполните проверки: они подтверждают не только наличие файлов, но и поведение сценариев, ошибки входа и воспроизводимость записанных результатов. Затем запустите встроенный PHP-сервер на loopback-адресе.
Откройте http://127.0.0.1:8193/ в браузере. Если порт занят, выберите свободный и используйте тот же адрес в браузере. Остановка выполняется Ctrl+C в терминале сервера. Не открывайте index.html двойным щелчком через file://: загрузка JSON и запрос к PHP требуют HTTP. Встроенный сервер удобен для знакомства и разработки, но не является способом публикации приложения в интернете.
Готовый public уже содержит CSS и JavaScript, поэтому Node.js для первого запуска не нужен. Он потребуется, если вы меняете интерфейс и хотите заново собрать локальные ресурсы: npm.cmd ci –ignore-scripts, npm.cmd run build и npm.cmd test. Обратите внимание на текущую папку: именно в ней находятся composer.json, fixtures и каталог public.
php --version
composer install --no-interaction --no-plugins --no-scripts
composer check-platform-reqs
composer test
composer verify
php -S 127.0.0.1:8193 -t public
Linux: локальная работа и подготовка к VPS
На Linux используется тот же проект и тот же lock-файл. Нужны поддерживаемый PHP CLI, Composer и расширения, требуемые зависимостями. В Ubuntu 24.04 стандартный PHP 8.3 подходит этому проекту; в другой системе проверьте имена пакетов и фактическую версию, а не копируйте название службы автоматически. Команда check-platform-reqs проверяет реальное окружение исполнения.
Composer лучше запускать от пользователя развёртывания, а не от root. Если он ещё не установлен, используйте официальный установщик и проверку подписи. Сначала добейтесь успешного CLI-запуска. Это отделяет ошибки PHP и зависимостей от конфигурации Nginx, Caddy, DNS и сертификатов.
Команды npm в Linux пишутся без суффикса .cmd. Готовую статическую часть можно собирать на отдельной машине и переносить вместе с релизом, а на сервере устанавливать только PHP-зависимости. Учебный сервер php -S по-прежнему привязывайте к 127.0.0.1. Для постоянной публичной работы ниже разобран PHP-FPM с отдельным document root.
sudo apt update
sudo apt install php8.3-cli php8.3-fpm php8.3-mbstring php8.3-xml unzip
php --version
composer install --no-interaction --no-plugins --no-scripts
composer check-platform-reqs
composer test
composer verify
php bin/render.php card fixtures/card.json
Loader, Environment и render: минимальная сборка
Loader отвечает за источник шаблонов. В мастерской FilesystemLoader получает один известный каталог templates. Environment хранит настройки исполнения: стратегию экранирования, строгость, кеш и правила обновления. Метод render выбирает шаблон по его имени, принимает данные и возвращает строку. Полный фреймворк для этого не требуется.
В нашем приложении имя шаблона берётся из серверной таблицы сценариев. Пользователь выбирает card, list или email, а PHP сам решает, какой файл открыть. Это удобнее и понятнее, чем принимать произвольный путь из URL. Объекты входа тоже ограничены: мастерская работает со строками, числами, логическими значениями, массивами и JSON-объектами.
Начальный вариант с cache=false подходит для небольшой демонстрации и не требует прав записи. В рабочем приложении файловый кеш можно включить отдельно, не меняя структуру шаблонов. Подробности окружения и загрузчиков есть в официальном API Twig. Не создавайте новое окружение для каждого маленького фрагмента одной страницы без причины: обычно настройки собирают один раз на запрос.
require __DIR__.'/vendor/autoload.php';
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
$twig = new Environment(new FilesystemLoader(__DIR__.'/templates'), [
'autoescape' => 'html',
'strict_variables' => true,
'cache' => false,
]);
echo $twig->render('card.html.twig', [
'customer' => ['name' => 'Анна', 'active' => true],
]);
Пример 1. Карточка клиента и значения по умолчанию
Карточка показывает имя, организацию, почту, статус и заметку. В PHP это небольшой подготовленный массив; Twig отвечает только за оформление. Вывод через двойные фигурные скобки проходит HTML-экранирование. Если в имени окажутся кавычки или угловые скобки, они не должны сломать тег и стать новой разметкой.
У разных полей разный смысл отсутствия. Для компании удобно использовать default: пустое значение заменяется понятной подписью. Для логического active важно сохранить false, поэтому используется null-coalescing оператор ??, а затем явный выбор текста статуса. Для заметки пустая строка может означать намеренно очищенное поле; заменять её автоматически бывает неправильно.
Попробуйте три изменения: удалите company, выставьте active в false, затем замените note пустой строкой. Сравните исходный HTML и интерфейс. Такой тест полезнее общего правила «везде ставить default», потому что показывает реальные значения предметной области. Значение по умолчанию — часть контракта данных, а не просто способ спрятать ошибку в шаблоне.
<article class="card"><span class="eyebrow">Карточка клиента</span><h1>{{ customer.name }}</h1><p>{{ customer.company|default('Компания не указана') }}</p><dl><dt>Почта</dt><dd>{{ customer.email|default('Нет адреса') }}</dd><dt>Статус</dt><dd>{{ customer.active ?? true ? 'Активен' : 'Отключён' }}</dd></dl><p>{{ customer.note ?? 'Заметки пока нет' }}</p></article>
Пример 2. Список заявок и пустое состояние
Список должен объяснять происходящее, когда записи есть, и когда их пока нет. Конструкция for выводит карточки, loop.index даёт понятную нумерацию, а else внутри цикла обрабатывает пустую коллекцию. Это помогает не оставлять посетителю белую область без объяснения: пустой ответ API и ошибка загрузки — разные состояния интерфейса.
В мастерской tasks содержит несколько заявок с ответственным и признаком готовности. Поменяйте done, добавьте запись, затем замените массив на []. Вы увидите, что пустое состояние формируется серверным шаблоном, а не отдельной случайной проверкой в JavaScript. При этом шаблон не ищет заявки в базе: порядок, фильтры и доступные записи уже определены приложением.
В рабочем сервисе дополните пустое состояние конкретным следующим действием: сбросить фильтр или создать заявку. Не показывайте кнопку создания человеку без такого права. Сначала PHP решает, разрешено ли действие, затем передаёт соответствующий флаг в представление. Шаблон остаётся простым, а авторизация проверяется в настоящем обработчике независимо от видимости кнопки.
<section><h1>{{ title }}</h1><ol>{% for task in tasks %}<li><strong>{{ loop.index }}. {{ task.title }}</strong><span class="badge">{{ task.done ? 'Готово' : 'В работе' }}</span><p>{{ task.owner|default('Не назначен') }}</p></li>{% else %}<li class="empty">Заявок пока нет. Добавьте первую запись в JSON.</li>{% endfor %}</ol><p>Всего: {{ tasks|length }}</p></section>
Пример 3. Один layout, две разные страницы
У обзора и карточки заявки обычно общие шапка, типографика и нижняя часть страницы. Наследование позволяет описать этот каркас один раз в base.html.twig, а содержимое страниц разместить в блоках. Дочерний шаблон использует extends и заменяет только нужные участки. В мастерской переключатель показывает обзор и детальную страницу на одних учебных данных.
Обратите внимание на footer: в детальной странице он дополняет родительское содержимое через parent. Это полезно, когда нужно сохранить общую подпись, но добавить пояснение конкретной страницы. Если блок должен полностью отличаться, вызов parent не требуется. Не делайте слишком глубокую цепочку наследования: два или три понятных уровня проще поддерживать, чем несколько скрытых переопределений.
На практике базовый layout часто содержит полноценный HTML-документ, meta viewport и подключение локальных CSS. В нашей мастерской он формирует фрагмент внутри изолированного предпросмотра, поэтому внешнюю оболочку добавляет интерфейс. При переносе в самостоятельный сайт выберите одну оболочку документа и избегайте вложенных html/body.
{% extends 'base.html.twig' %}{% block title %}Заявка {{ number }}{% endblock %}{% block content %}<p>{{ title }}</p><dl><dt>Ответственный</dt><dd>{{ name }}</dd><dt>Открыто</dt><dd>{{ open }}</dd></dl>{% endblock %}{% block footer %}{{ parent() }} Детальная страница меняет и нижний блок.{% endblock %}
Пример 4. Include и явный контекст карточки
Include подходит для повторяемого фрагмента: карточки участника, строки таблицы, блока контактов. По умолчанию включённый шаблон может видеть текущий контекст. Это удобно, но создаёт неочевидную зависимость: перенесли карточку на другую страницу, а она ждёт внешнее поле, о котором никто не помнит.
В примере мы передаём person явно и добавляем only. Карточка получает данные конкретного участника и не рассчитывает на случайные переменные страницы. Попробуйте изменить внешнее поле role: подпись в карточке останется той, что находится в team. Затем добавьте ещё одного участника — разметка для него будет собрана тем же фрагментом.
Такой подход упрощает повторное использование и проверку шаблона. Список входов виден рядом с include, а карточку можно протестировать небольшим отдельным массивом. Подробности передачи контекста изложены в документации include. Имя включаемого файла в этом проекте фиксировано в доверенном шаблоне; JSON не может подменить его путём.
<section><h1>{{ title }}</h1>{% for member in team %}{% include 'partials/member.html.twig' with {person: member} only %}{% endfor %}</section>
Пример 5. Макрос поля: label, значение и ошибка
Формы быстро становятся источником мелких несоответствий: у одного поля нет подсказки, у другого ошибка не связана с input, у третьего пропадает введённое значение после неудачного отправления. Макрос позволяет описать эту структуру один раз и передавать конкретные подписи, значения и сообщения.
Наш макрос принимает id, label, value, error и help. Он связывает label с input, добавляет aria-describedby и выставляет aria-invalid при наличии ошибки. В интерфейсе удалите текст errors.email и сравните исходный HTML. Пример показывает только разметку: предпросмотр изолирован, форма ничего не отправляет и не сохраняет.
Twig не проверяет корректность почты и не выдаёт CSRF-токен сам по себе. В настоящем приложении PHP сначала обрабатывает запрос, проверяет токен и данные, затем возвращает структуру errors. Для правил можно использовать DTO и Validator из отдельного материала. Уникальные id нужны и при повторении формы: один и тот же id для нескольких полей ломает связь с label.
{% import 'macros/fields.html.twig' as fields %}<section><h1>Карточка заявки</h1>{{ fields.input('client-name', 'Имя клиента', name, errors.name|default(''), 'Как обращаться в ответе') }}{{ fields.input('client-email', 'Почта', email, errors.email|default(''), 'Пример формы; отправки нет') }}<p>Поля показывают HTML, который сгенерировал Twig. Изменение предпросмотра не отправляет форму.</p></section>
Пример 6. HTML-письмо и текстовая альтернатива
В примере письмо сообщает, что заказ готов. PHP передаёт имя, номер, позиции, готовые строковые суммы и сообщение. Twig собирает HTML-таблицу и отдельный текстовый вариант. Кнопки позволяют скачать оба результата, но отправки почты на странице нет. Это полезное разделение: сначала проверить содержимое и оформление, затем подключить транспорт.
Деньги в шаблоне не пересчитываются. Форматированные суммы готовит приложение с выбранными правилами округления и валютой. Если в JSON изменить позиции, итог автоматически не изменится — он тоже входное поле. Такое поведение указано в подсказке намеренно: оно показывает границу между бизнес-расчётом и представлением.
Текстовый шаблон использует отключение HTML-экранирования только внутри файла, предназначенного для plain text. Амперсанд в имени остаётся амперсандом, а не HTML-сущностью. Не переносите эту настройку в HTML-шаблон. Настоящие письма также требуют проверки в почтовых клиентах, адаптивной таблицы и транспорта с корректными заголовками; браузерный предпросмотр не подтверждает совместимость всех почтовых программ.
$html = $twig->render('email.html.twig', $data);
$text = $twig->render('email.txt.twig', $data);
// Передайте HTML и plain text выбранному почтовому транспорту.
// render не отправляет сообщение и не вычисляет суммы заказа.
Пример 7. Экранирование: данные не становятся HTML
Введите в message строку с strong, script или кавычками. В предпросмотре она должна остаться текстом. В исходном HTML видны сущности, которые не позволяют закрыть текущий тег или атрибут. В мастерской данные также выводятся в value внутри кавычек, поэтому можно проверить два обычных контекста: текст элемента и значение атрибута.
Экранирование зависит от места вывода. HTML-стратегия не является универсальной защитой для JavaScript, CSS и адресов. Например, безопасное отображение кавычек в href не означает, что разрешена любая схема URL. Если приложение принимает ссылку, отдельно проверяйте допустимые протоколы и назначение. Наши учебные шаблоны не подставляют пользовательские значения в исполняемый JavaScript или адрес внешнего ресурса.
Фильтр raw отключает экранирование. Он нужен только там, где приложение осознанно передаёт уже доверенный HTML после соответствующей обработки. Ставить raw ради «красивого текста из формы» опасно. В официальном описании escape перечислены разные стратегии; выбирайте контекст явно и не смешивайте экранирование с проверкой бизнес-данных.
<p>{{ message }}</p>
<input value="{{ message }}">
<pre>{{ payload|json_encode(constant('JSON_UNESCAPED_UNICODE')) }}</pre>
Пример 8. Strict variables: обнаружить потерянное поле
В строгом сценарии customer содержит имя, но не содержит contractNumber. При включённом strict_variables Twig возвращает ошибку с именем поля и строкой шаблона. Это ожидаемый результат примера. Добавьте номер договора в JSON, повторите запуск и получите HTML. Затем уберите поле и сравните поведение с выключенной строгостью.
Строгий режим особенно полезен для писем и отчётов. Пустой номер договора в уже отправленном письме обнаруживается поздно, а ошибка на этапе формирования позволяет остановить операцию и сохранить понятную диагностику. Однако не каждое поле обязательно. Для необязательных значений используйте осмысленные default, ?? или проверку defined, чтобы отсутствие было частью контракта.
Не стоит выключать строгость только потому, что один шаблон падает. Сначала выясните: данные действительно необязательны или приложение забыло их подготовить. В первом случае измените шаблон и пояснение пользователю; во втором исправьте источник. Мастерская показывает сообщение без стека и серверных путей, а подробную техническую диагностику рабочего проекта следует хранить в серверных логах.
<section><h1>{{ title }}</h1><p>Получатель: {{ customer.name }}</p><p>Номер договора: {{ customer.contractNumber }}</p><p>Замечание: {{ note|default('Нет замечаний') }}</p></section>
Пример 9. Компиляция и кеш: что именно сохраняется
Twig преобразует шаблон в PHP-код. Файловый кеш позволяет повторно использовать этот код между запросами, не проходя весь путь компиляции каждый раз. Он не является кешем готового ответа: один и тот же шаблон с именами Анна и Борис должен дать два разных результата. Путать эти два вида кеша особенно неприятно в личных кабинетах.
Публичный сценарий выполняет настоящую компиляцию фиксированного шаблона и показывает количество байтов исходника, результата и скомпилированного кода. Он не пишет данные или кеш на сервер сайта. Для проверки файлового механизма скачайте проект и выполните bin/cache-demo.php: команда создаёт var/cache, делает две отрисовки и выводит фактическое число файлов и различие HTML.
Сравнение размеров не является измерением производительности. Скорость зависит от шаблона, PHP, OPcache, файловой системы и прогрева. Если вам нужен вывод о задержке, измеряйте одинаковый сценарий в своём окружении и отделяйте первый запуск от повторных. В нашей статье кеш объясняется проверяемым механизмом, без обещания одинакового ускорения на любой машине.
php bin/cache-demo.php
Локальное изменение шаблонов и добавление своего сценария
В архиве все шаблоны обычные текстовые файлы. Измените card.html.twig, запустите CLI с fixtures/card.json и посмотрите результат. Такой путь удобен для изучения циклов, наследования и макросов: вы контролируете PHP-проект и можете пользоваться своим редактором. Для новых данных сохраните отдельный JSON и передайте его вторым аргументом команды.
Чтобы добавить публичный сценарий в эту мастерскую, разработчик явно дополняет серверную таблицу Workshop::TEMPLATES, список примеров и проверки. Произвольное имя файла от посетителя не превращается в render. Это ограничение относится к демонстрации на сайте, а не к возможностям Twig в вашем доверенном коде.
Если продукту действительно нужен редактор шаблонов для пользователей, это отдельная задача: права, допустимые функции, лимиты, изоляция и политика обновлений. Автоматическое HTML-экранирование не превращает любой шаблонный код в безопасный. Не заменяйте фиксированную карту сценариев на createTemplate с содержимым HTTP-запроса, считая, что autoescape решает все риски исполнения.
php bin/render.php card fixtures/card.json
php bin/render.php email fixtures/email.json
# После намеренного изменения шаблонов:
composer test
composer build
composer verify
Данные для шаблона: массивы, DTO и подготовленные значения
Хороший вход шаблона напоминает контракт ответа API: названия понятны, обязательные поля присутствуют, даты и деньги уже подготовлены для выбранного сценария. Не передавайте весь большой объект приложения только ради имени клиента. Чем меньше контекст, тем легче понять, какие сведения действительно попадут на страницу или в письмо.
Twig умеет обращаться к свойствам и методам объектов, но в этой мастерской JSON превращается в ограниченный набор массивов и скаляров. В настоящем проекте можно использовать специальный view model или DTO. Он должен содержать то, что нужно представлению, и не запускать неожиданный запрос к базе при чтении каждого свойства.
Например, приложение может подготовить customerName, statusLabel и totalFormatted, а шаблон — выбрать расположение и классы компонентов. Это не запрещает простой if или цикл. Разделение нужно для бизнес-решений: доступ к записи, стоимость, скидка, часовой пояс и факт оплаты определяются до вывода. Тогда тест шаблона проверяет оформление, а тест сервиса — правильность расчёта.
$view = [
'customer' => 'Анна',
'order' => [
'number' => 'A-1042',
'items' => [['title'=>'Диагностика','quantity'=>1,'total'=>'1 500,00 ₽']],
'total' => '1 500,00 ₽',
],
'message' => 'Документы готовы к выдаче.',
];
$html = $twig->render('email.html.twig', $view);
Ошибки HTTP, ожидание и устаревший результат
В интерфейсе важно показывать, к какому вводу относится результат. После изменения JSON предыдущая отрисовка помечается неактуальной, скачивание отключается и требуется новый запуск. Если пользователь быстро переключил сценарий, ответ старого запроса не должен заменить новый экран. Для этого клиент использует номер ревизии и AbortController.
Кнопка остановки прекращает ожидание в браузере. Она не обещает откат серверной операции: короткий PHP-запрос мог уже завершиться. Здесь это безопасно для данных, потому что обработчик только формирует строку и не выполняет запись. В приложении с изменением заказа или оплатой такая семантика должна обсуждаться отдельно.
Лимит ожидания составляет двенадцать секунд. При ошибке сети или ответе не в JSON интерфейс объясняет, что проверить, и позволяет повторить попытку. Запись исходного опыта открывается отдельной кнопкой; автоматически подменять ею неудавшийся живой запрос нельзя. Иначе посетитель увидит красивый результат, который не имеет отношения к его данным.
const controller = new AbortController();
const response = await fetch('api.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({scenario: 'card', data, options: {strict: true}}),
signal: controller.signal
});
// Abort останавливает ожидание клиента; серверный render мог завершиться.
Публичный обработчик: понятные ограничения
Мастерская принимает POST с application/json и только три верхних поля: scenario, data и options. Сценарий выбирается из девяти известных значений, настройки ограничены строгим режимом и выбором страницы layout. Размер HTTP-запроса ограничен тридцатью двумя кибибайтами, данных — шестнадцатью. Коллекция содержит не более тридцати элементов, структура — не более восьми уровней и двухсот пятидесяти значений.
Эти ограничения делают учебный endpoint предсказуемым. Они не заменяют защиту настоящего сервиса от большого числа запросов: на своём сервере добавляйте ограничение частоты, права доступа и наблюдение за ресурсами. Если шаблон читает данные пользователя, проверка авторизации выполняется перед подготовкой контекста, а не только через скрытую кнопку в интерфейсе.
Коды ошибок различаются по смыслу. 400 сообщает о нечитаемом JSON, 413 — о размере HTTP, 415 — о неподходящем Content-Type, 422 — о входных данных или проблеме шаблона. 503 означает отсутствующий runtime. Посетителю показывается короткое объяснение; служебный стек и абсолютные пути не возвращаются в JSON.
{
"scenario": "card",
"data": {"customer": {"name": "Анна", "active": false}},
"options": {"strict": true}
}
Linux/VPS: PHP-FPM и правильный document root
Для постоянной работы используйте веб-сервер и PHP-FPM. Разместите релиз, например, в /srv/twig-workshop/current, но document root направьте только на public. Каталоги templates, src и vendor, а также composer.lock не должны становиться публичным деревом сайта. Пользователю PHP достаточно чтения, потому что endpoint этой мастерской работает без файлового кеша.
Зависимости устанавливайте от пользователя развёртывания. Для production-сборки можно исключить dev-пакеты и включить оптимизированную автозагрузку. Затем проверьте реальные требования PHP и выполните проверки проекта. Убедитесь, что CLI и FPM используют совместимые версии: успешный php –version в терминале ещё не доказывает версию процесса, который отвечает на HTTP.
Не переносите локальный php -S на внешний интерфейс как постоянный сервер. Настройте домен, TLS и обычный процесс обновления релиза. В архиве есть отдельные примеры Caddy и Nginx; замените домен, каталог и сокет своими значениями. На существующем сервере добавляйте отдельный сайт или согласованный location, сохраняя остальные конфигурации.
cd /srv/twig-workshop/current
composer install --no-dev --classmap-authoritative --no-interaction --no-plugins --no-scripts
composer check-platform-reqs --no-dev
composer test
composer verify
sudo systemctl status php8.3-fpm --no-pager
Caddy и Nginx: подключение PHP без лишнего доступа
Caddy может передать PHP-запрос в известный сокет FPM и обслужить остальные файлы из public. Для реального публичного домена нужны корректные DNS и доступность сервера для выпуска сертификата. Учебное имя twig.example.test в конфигурации необходимо заменить; оно не предназначено для получения публичного сертификата.
В варианте Nginx обработка PHP разрешена только для /api.php, остальные PHP-пути отклоняются. Это соответствует устройству конкретной мастерской. Если позже добавите маршрутизатор приложения, правило нужно менять осознанно, а не переносить готовую конфигурацию без понимания. Пример Nginx в архиве слушает HTTP и предполагает настроенный TLS-фронтенд либо дальнейшую настройку HTTPS.
Перед reload обязательно выполните проверку синтаксиса. После применения откройте api.php и саму мастерскую, выполните один запрос и проверьте загрузку JavaScript/CSS. Ответ readiness подтверждает только доступность runtime; правильную работу сценария подтверждает фактическая отрисовка. Серверные журналы должны показывать причину ошибки, не раскрывая её целиком посетителю.
twig.example.test {
root * /srv/twig-workshop/current/public
request_body {
max_size 32KB
}
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
header X-Content-Type-Options nosniff
}
Кеш на Linux: права, релизы и обновление шаблонов
Если ваше приложение включает файловый кеш Twig, выделите отдельный каталог вне public и дайте права записи только тому пользователю, который его обслуживает. Не выдавайте 777 всему проекту: исходники и шаблоны обычно должны оставаться доступными лишь для чтения процессу PHP. Публичная мастерская не требует этого каталога; он нужен для вашего режима с кешем.
Параметр auto_reload помогает учитывать изменения шаблона, но схема обновления production всё равно должна быть понятной. При отключённом автоматическом обновлении старый скомпилированный шаблон может продолжать использоваться до смены кеша. Отдельный путь для нового релиза часто проще контролировать, чем удаление общего каталога на работающем сервере.
Помните и об OPcache PHP: это другой слой хранения кода. Изменение файла на диске и обновление процесса могут иметь собственные правила. Не применяйте широкую команду удаления к вычисленному пути без проверки. Сначала определите владельца, фактический каталог и способ переключения релиза, затем обновляйте только ресурс этого приложения.
sudo install -d -o www-data -g www-data -m 0770 /srv/twig-workshop/var/cache
# Этот путь указывается в Environment вашего приложения.
# Публичный api.php данного проекта остаётся с cache=false.
Исходники, тесты и воспроизводимость
Архив содержит composer.json и lock, templates, PHP-классы, endpoint, CLI, fixtures и готовый public. PHP-рецепт из мастерской запускается из корня полного проекта после установки Composer-зависимостей. Входные данные в рецепте закодированы в Base64, чтобы кавычки не превращались в PHP-синтаксис; это не шифрование и не способ хранения секретов.
Проверки покрывают все девять сценариев, сохранение false, пустые списки, наследование, изоляцию include, ошибки макроса, HTML-экранирование, строгий режим, текстовое письмо и границы endpoint. Отдельно проверяется воспроизводимость записей настоящего PHP. Клиентские тесты проверяют форму входа, ограничение UTF-8, ревизии запросов и политику предпросмотра.
После изменения шаблона сначала выполните тесты и изучите новый результат. Только потом обновляйте записи командой composer build и проверяйте их composer verify. Автоматическое обновление эталона без просмотра легко скрывает ошибку. Полные лицензии зависимостей входят в THIRD-PARTY-LICENSES.txt; собственный код примеров распространяется под MIT.
composer test
composer verify
npm ci --ignore-scripts
npm run build
npm test
composer audit --no-interaction
npm audit --audit-level=low
Частые вопросы: пустое поле, лишние сущности и неверный результат
Если поле исчезло, сначала посмотрите входной JSON и включите strict_variables. Иногда причина в названии: contract_number и contractNumber — разные ключи. Если поле действительно необязательно, задайте правило для отсутствия и пустого значения, а не убирайте строгость для всего приложения. Если шаблон выводит Array или сообщает о преобразовании типа, вместо строки пришёл массив: исправляйте контракт данных.
Если в HTML видны amp или lt, различайте исходник и отображаемую страницу. В исходнике сущности ожидаемы, в интерфейсе браузер показывает соответствующие символы. Если посетитель видит буквальный текст <, данные могли экранировать ещё до Twig. Обычно в контекст передают обычный текст и экранируют на этапе вывода, избегая повторной обработки.
Если изменение шаблона не видно, проверьте выбранный файл, путь loader, кеш Twig, OPcache и фактический релиз. Если не работает только браузерная мастерская, откройте её через HTTP, проверьте api.php и MIME JavaScript. Если письмо выглядит иначе в почте, причина может быть в ограничениях клиента: браузерный preview не заменяет тест отправленного письма.
Как внедрить Twig в свой сервис по шагам
Выберите один законченный сценарий: письмо о готовности заказа, карточку клиента или страницу отчёта. Сначала зафиксируйте входные данные небольшим массивом и сохраните учебный пример. Затем вынесите разметку в шаблон, включите autoescape и strict_variables, добавьте понятные правила необязательных полей. Получите результат через CLI, чтобы исключить лишние слои при первом запуске.
После этого подключите шаблон к существующему обработчику. Проверьте длинное имя, пустой список, кавычки, отсутствующее поле и сообщение об ошибке. Посмотрите страницу на узком экране: серверный шаблонизатор формирует HTML, но адаптивность зависит от вашей разметки и CSS. Для форм проверьте label, порядок фокуса и доступное объяснение ошибок.
Когда один сценарий работает, выделяйте общие include, layout и макросы на основе реального повторения. Так библиотека шаблонов будет отражать задачи проекта. Дальнейшие конструкции изучайте по руководству Twig для шаблонов, а готовую мастерскую используйте как небольшой воспроизводимый проект с проверяемыми границами.


