Обычно за шаблоны в PHP платят тремя вещами: композером с десятком пакетов, отдельным кэшем, который надо чистить, и заменой <?= $var ?> на что-то, что экранирует само. HTML.PHP 6.0.0 пытается обойтись одним файлом: шаблонизатор PHP без зависимостей, с экранированием по умолчанию, полным набором директив Apache SSI и gettext-совместимой локализацией. Ниже — что внутри, как это проверено на живом PHP 8.5, где проходит граница разумного применения и на чём спотыкаются чаще всего.

html php cover

TL;DR: что это и как проверить за десять минут

  1. Что это. Библиотека обработки шаблонов: подстановки с фильтрами, циклы, условия, наследование макетов, частичные шаблоны с областью видимости.
  2. Что ещё. Полный процессор Apache SSI (#if, #exec, #printenv, #block) и слой i18n с каталогами PO/MO/JSON/PHP.
  3. Зависимостей нет. Один файл: php83/html.php для PHP 8.3–8.5 или php85/html.php только для 8.5.
  4. Лицензия MIT — в этой версии она прописана во всех манифестах и проверяется тестом.
  5. Движок тот же, что в 5.0.0. Ни одна конструкция шаблонов, опция или метод API не изменились.
curl -fsSL -o html.php \
  https://github.com/paulmann/HTML.PHP/releases/download/v6.0.0/html-php-6.0.0-php83.php
sha256sum html.php   # 7df928018f8f43f7e9d402e7a5ff40fed73f490f549b5a69e8e07afa1f1477b6
php -r "require 'html.php'; echo HtmlPhp\Engine::create()->renderString('Hello %{name}!', ['name' => '<World>']);"
# Hello &lt;World&gt;

Проблема, которую это решает

Шаблоны в PHP-проекте почти всегда начинаются одинаково: <?= htmlspecialchars($title) ?>. Через месяц кто-то забывает htmlspecialchars, и в разметку приезжает пользовательский текст. Через два — появляется «свой» мини-шаблонизатор с регулярками. Через три — в проект добавляется Twig, а вместе с ним композер, кэш шаблонов, отдельный синтаксис, и вопрос «где это компилируется» на каждом новом сервере.

Второй пласт — локализация. Gettext в PHP работает, но обвязка вокруг него (каталоги, множественное число, извлечение строк, аудит непереведённого) обычно пишется руками и живёт в каждом проекте по-своему.

Третий — SSI. Классические .shtml с <!--#include virtual=...--> обрабатывает сам веб-сервер, и это привязывает шаблоны к Apache с mod_include. На nginx такой страницы не будет, а перенести сайт — значит переписать вставки.

HTML.PHP собирает все три истории в одну библиотеку: шаблон обрабатывается в PHP, SSI разбирается в PHP, каталоги перевода читаются в PHP. От веб-сервера не зависит ничего.

Что такое HTML.PHP

flowchart TB
  APP["приложение на PHP"] --> ENG["Engine: единая точка входа"]
  ENG --> TPL["шаблоны: %-подстановки, фильтры, выражения"]
  ENG --> SSI["слой Apache SSI: if, exec, block, printenv"]
  ENG --> I18N["i18n: каталоги PO, MO, JSON, PHP"]
  ENG --> CACHE["кэш рендера на файлах"]
  TPL --> LINT["линтер шаблонов для CI"]
  I18N --> PLUR["правила множественного числа для 60+ языков"]
Свойство Значение
Тип библиотека обработки шаблонов, не приложение
Рантайм PHP 8.3+ (php83/html.php, CI: 8.3, 8.4, 8.5) или PHP 8.5+ (php85/html.php)
Платформа Windows, Linux, macOS — где есть PHP
Точки входа единый файл, src/ через autoload.php, Composer PSR-4, CLI bin/htmlphp
Зависимости нет
Сеть нет: ни телеметрии, ни исходящих запросов
Запись на диск только если включён кэш рендера, файловый лог или экспорт POT
Лицензия MIT, включая исходники, оба файла и плагин WordPress

Это библиотека, а не фреймворк: нет роутера, базы, демона и веб-интерфейса. Её место — внутри существующего приложения, между контроллером и выводом.

Три части, которые обычно живут раздельно

Часть Что делает
Шаблоны подстановки %{}, #{}, @{} с фильтрами, циклы, условия, макеты, частичные шаблоны
SSI разбор директив mod_include в PHP: #include, #if, #exec, #printenv, #block/#stub
i18n gettext-совместимые каталоги PO, MO, JSON, PHP; правила множественного числа для 60+ языков

Каждая часть работает и сама по себе: можно взять только шаблоны, можно прогнать SSI по готовому HTML из базы, можно использовать только перевод.

Возможности

Шаблоны

Что Как выглядит
Подстановка %{title} — с автоэкранированием; #{...} и @{} для других режимов
Фильтры %{price|number:2}, %{name|upper} и десятки других
Выражения %{= done / total * 100 | round:1 } — арифметика прямо в подстановке
Динамические ключи %{= prices[currency]}, %{= rows[i].name}
Косвенность %{= get(fieldName)} — имя поля берётся из данных
Циклы <!--@Loop rows--> … <!--@Else--> … <!--@EndLoop-->, %{_position}
Условия <!--@If total > 100 --> … <!--@Else--> … <!--@EndIf-->
Вычисления <!--@Set m="price * 2"-->
Наследование <!--@Extends "base.html"--> для макетов
Частичные шаблоны с ограниченной областью видимости

Фильтры арифметики: add sub mul div mod pow sqrt pct sum avg clamp. Диапазоны a..b в выражениях работают и индексируются: %{= (1..3)[1]} даёт второй элемент.

Локализация

Что Как работает
Каталоги PO, MO, JSON, PHP — что удобнее в проекте
Множественное число правила для 60+ языков, включая русские формы
Извлечение строк i18n:extract собирает POT из шаблонов
Аудит отчёт о непереведённых строках
В шаблоне @{list.empty} — термин переводится, если список пуст

SSI

Директива Назначение
#include вставка файла, как в mod_include
#if / #elif / #else условия с выражениями
#exec выполнение команды — по умолчанию запрещено (ssi.exec_mode: deny)
#printenv вывод переменных окружения
#block / #stub блоки с подстановкой заглушек

processSsi() работает и над произвольной строкой — например, над содержимым записи WordPress, пришедшим из базы.

Архитектура внутри

flowchart LR
  F["шаблон .html"] --> PARSE["разбор: подстановки и директива @Loop/@If"]
  PARSE --> EXPR["выражения в песочнице: без eval, только белый список"]
  EXPR --> ESC["экранирование по умолчанию"]
  ESC --> TR["перевод через каталог i18n"]
  TR --> OUT["готовый HTML"]
  CACHE["кэш рендера"] -.-> OUT

Устройство слоёное, и это видно по структуре: фасад Engine, контейнер, рендерер с конвейером «разбор → макеты → дерево → SSI → кэш», процессор SSI, переводчик, интерфейсы кэша и логгера, диспетчер событий, диагностика и адаптеры WordPress.

Слой Ответственность
Engine фасад: render, renderFile, renderString, lint, t, processSsi, stats
Template\Renderer конвейер разбора и вывода
Expression безопасный вычислитель выражений
Ssi\Processor сканер директив и реестр политик
I18n\Translator каталоги, формы множественного числа, делегирование WordPress
CacheInterface драйверы file, apcu, array, null, wp, psr16
Log\LoggerInterface null, array, file, errorlog, syslog, wp, psr3

Жёсткое правило, которое проверяется тестом на уровне токенов: ядро никогда не вызывает eval() и не читает $GLOBALS и суперглобальные массивы. Единственные разрешённые точки соприкосновения с внешним миром — явный помощник ввода и адаптеры WordPress. Для библиотеки, которая работает с пользовательским контентом, это правильная граница.

Замеры и цифры

Проверено 09.11.2026: скачаны оба файла релиза, сверены контрольные суммы, затем библиотека запущена на живом PHP.

Что измерено Значение Как
html-php-6.0.0-php83.php 530 172 байта, SHA-256 7df928…47b6 gh release download + Get-FileHash
html-php-6.0.0-php85.php 531 994 байта, SHA-256 19c4e4…0274 там же
Совпадение с хешами из заметок к релизу оба совпали сверка вручную
Объём файлов 16 269 и 16 316 строк подсчёт строк
Проверка синтаксиса php -l — ошибок нет CLI
Версия PHP на машине 8.5.0 (cli, NTS, Visual C++ 2022 x64) php -v
Тесты по бейджу README 391 тест, 389 пройдено, 2 пропущено README.md
PHPStan уровень 6, чисто бейдж и заметки
Оформление кода php-cs-fixer чист на 143 файлах заметки к релизу
HTML.PHP 6.0.0: размеры файлов, хеши и результаты прогона
HTML.PHP 6.0.0: размеры файлов, контрольные суммы и результаты прогона

Прогон в один заход, восемь проверок:

Проверка Код Результат
Экранирование renderString('%{title}') Hello &lt;World&gt; — теги обезврежены
Арифметика %{= done / total * 100 \| round:1 }% 14.8%
Формат числа %{price\|number:2} при локали ru 1 234,50
Цикл @Loop с %{_position} и upper [1:ADA][2:GRACE]
Условие @If total > 100 много
Вычисление @Set m="price * 2" 2469.0
Диапазон %{= (1..3)[1]} 2
Перевод $engine->t('{0} files', [42]) 42 files

Отдельно проверил stats(): возвращает template, startedAt, durationMs, cacheHit, substitutions, loops — этого достаточно, чтобы однажды выяснить, почему страница стала медленной, без профайлера.

Что замеры не показывают: производительность на реальном сайте, поведение под нагрузкой и работу SSI-директив #exec/#block — я их не запускал. Первое требует стенда с трафиком, второе — стенда с Apache-подобным окружением.

Сильные стороны продукта

  • Ноль зависимостей. Один файл, никакого композера — и никаких «а почему у вас не установился пакет».
  • Экранирование по умолчанию. Забыть htmlspecialchars технически невозможно: вывод экранируется, пока не попросишь обратного.
  • Нет eval и суперглобальных массивов в ядре. Проверяется тестом на уровне токенов, а не обещанием в документации.
  • SSI без Apache. Директивы разбираются в PHP, поэтому шаблоны переносятся на nginx и FPM без переписывания.
  • Локализация в комплекте. Каталоги, множественное число и извлечение строк — не отдельный проект.
  • Понятная конфигурация. Неизвестный ключ даёт исключение, а не молча игнорируется.
  • Диагностика из коробки. stats() и структурированный сборщик диагностик с устойчивыми кодами.
  • MIT. Коммерческое использование и закрытая сборка разрешены при сохранении уведомления.

Установка

Три пути, и первый — самый короткий.

# 1. из релиза: один файл, без Composer
curl -fsSL -o html.php \
  https://github.com/paulmann/HTML.PHP/releases/download/v6.0.0/html-php-6.0.0-php83.php
sha256sum html.php

# 2. Composer
composer require paulmann/html-php        # PHP 8.3/8.4
composer require paulmann/html-php85      # PHP 8.5

# 3. WordPress: плагин из каталога wordpress/html-php/
Требование Зачем Проверка
PHP 8.3 или новее оба файла отказываются работать на старом рантайме php -v
PHP 8.5+ для сборки 8.5 её манифест требует >=8.5 php -v
Расширения не обязательны mbstring, intl, apcu только улучшают поведение php -m
Записываемый каталог нужен, только если включён файловый кэш выбрать путь в cache.directory

Запуск и первый шаблон

require_once __DIR__ . '/html.php';

use HtmlPhp\Engine;

$engine = Engine::create([
    'templates' => ['paths' => [__DIR__ . '/templates']],
    'i18n'      => ['locale' => 'de', 'paths' => [__DIR__ . '/languages']],
    'cache'     => ['enabled' => true, 'driver' => 'file',
                    'directory' => sys_get_temp_dir() . '/htmlphp'],
]);

echo $engine->render('page.html', [
    'title' => 'Hello <World>',
    'rows'  => [['name' => 'Ada'], ['name' => 'Grace']],
]);
<h1>%{title}</h1>                      <!-- Hello &lt;World&gt; -->
<ul>
<!--@Loop rows-->
    <li>%{_position}. %{name|upper}</li>
<!--@Else-->
    <li>@{list.empty}</li>
<!--@EndLoop-->
</ul>
<!--#include virtual="/partials/footer.shtml" -->   <!-- Apache SSI -->

Проверка версии и работоспособности — две команды:

php bin/htmlphp version
php -r "require 'html.php'; echo HtmlPhp\Engine::create()->renderString('Hello %{name}!', ['name' => '<World>']);"

Если вторая строка вывела Hello &lt;World&gt;!, экранирование работает. Если вывела <World> — вы подключили не тот файл.

Настройка

flowchart TD
  CFG["Engine::create: массив опций"] --> TP["templates.paths"]
  CFG --> I18["i18n.locale и i18n.paths"]
  CFG --> CA["cache.enabled, driver, directory"]
  CFG --> SYN["syntax.expressions: выключатель выражений"]
  TP --> R["render ('page.html')"]
  I18 --> R
  CA --> R
  SYN --> R

Конфигурация — обычный массив в Engine::create(). CLI принимает тот же массив из PHP-файла: --config=FILE, файл обязан вернуть массив, иначе команда завершится кодом 2.

Ключ По умолчанию Что значит
syntax.auto_escape true экранировать вывод по умолчанию
syntax.expressions включены выключатель выражений в подстановках
syntax.allow_raw_filter — разрешать фильтр «вывести как есть»
ssi.enabled true обрабатывать SSI-директивы
ssi.exec_mode deny политика выполнения команд
cache.enabled false кэш рендера выключен
cache.driver file драйвер кэша
log.driver null логирование выключено
i18n.locale en язык по умолчанию

Два правила, которые стоит запомнить. Неизвестный ключ на любом уровне вложенности даёт ConfigException — опечатка не отключит защиту молча. И для шаблонов, которые правят пользователи, документация советует выключить syntax.allow_raw_filter и syntax.expressions: тогда из шаблона нельзя ни вывести сырой HTML, ни заставить библиотеку считать выражение.

Когда SSI в PHP, а не в Apache

Разница практическая. Классический mod_include обрабатывает вставки на уровне веб-сервера: страница .shtml уходит клиенту уже собранной, но собрать её может только Apache с включённым модулем. Стоит перенести сайт на nginx — вставки перестают работать, и в браузере видно <!--#include ... --> как текст.

Здесь разбор идёт в PHP: страница собирается в приложении, поэтому одинаково работает на Apache, на nginx с FPM, на IIS и в консоли. Плата — время PHP на разбор директив вместо C-модуля сервера; выигрыш — переносимость и то, что SSI можно применить к содержимому из базы.

Критерий mod_include SSI в PHP
Где работает только Apache с модулем везде, где есть PHP
Что обрабатывает только файлы на диске файлы и строки из базы
Политика #exec настройками сервера явная: по умолчанию deny
Стоимость C-модуль время PHP

Локализация

Сценарий Как решается
Строки в шаблонах @{key} — термин подставляется из каталога
Строки в коде $engine->t('{0} files', [42])
Множественное число правила для 60+ языков, включая русские формы

Русские формы — отдельная проверка: три разные строки для одного ключа (1 файл, 2 файла, 5 файлов), и правило выбирает нужную по числу. Без такого механизма шаблон пришлось бы обвешивать условиями, а условия в шаблоне — прямой путь к ошибкам. | Извлечение строк | i18n:extract → файл POT | | Аудит | отчёт о непереведённых ключах | | Каталог | PO, MO, JSON или PHP — на выбор проекта |

JSON и PHP-каталоги здесь не экзотика: они удобны, когда переводы правят не переводчики с Poedit, а разработчики в репозитории — diff читаемый, конфликтов меньше.

WordPress-мост

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

Что даёт Зачем нужно
Кэш на транзиентах не заводить отдельный каталог кэша на хостинге
Загрузчик шаблонов темы и плагины находят шаблоны в своих каталогах
Шорткоды вывод шаблона из содержимого записи
processSsi для контента SSI-директивы прямо в записях
CLI-команды обслуживание без админки

Для сайта на WordPress это тот случай, когда библиотека не спорит с платформой, а встаёт рядом: свои кэши, свои логи, свои шаблоны.

Модель безопасности: что именно проверяется тестом

Про «безопасный шаблонизатор» обычно говорят в маркетинговом смысле. Здесь это проверяемое утверждение, и его стоит разобрать по частям.

Утверждение Как проверяется
Ядро не вызывает eval() тест на уровне токенов сканирует исходники
Ядро не читает $GLOBALS и суперглобальные то же сканирование
Вывод экранируется по умолчанию syntax.auto_escape: true, проверено прогоном
Выражения не дают новый вектор переиспользуется тот же вычислитель, что с версии 4
Динамические ключи не доходят до объектов ключи ограничены массивами и свойствами данных
get() резолвится только по стеку областей не выходит за пределы переданных данных
Арифметика без INF и NAN результат ограничен, деление на ноль не даёт бесконечности
Нет исходящих запросов сетевого слоя нет; SSI-подзапросы -A/-U отклоняются с диагностикой

Два пункта из этого списка важны на практике. Первый — про выражения: арифметика в подстановках появилась в 5.0 и могла бы стать дырой, если бы её сделали через eval; здесь её отдали тому же песочничному вычислителю, что и условия в шаблонах, с белым списком функций. Второй — про ограничения итераций: на каждом обходе хостового итерируемого объекта стоит бюджет в 100 000 шагов, а кэш разобранных выражений ограничен 4096 записями в порядке FIFO. Иначе шаблон с большими данными превращается в способ съесть память.

Отдельно — #exec. В Apache эта директива выполняется процессом сервера, и её ограничивают настройками mod_include. Здесь политика явная и по умолчанию deny: пока её не включишь, #exec в шаблоне ничего не делает. Это правильный порядок вещей — сначала безопасно, потом удобно.

Диагностика и обслуживание

Инструмент Что даёт
$engine->stats() время, число подстановок, циклов, попаданий в кэш
lintDirectory() проверка шаблонов в CI тем же разборщиком, что и рендер
Сборщик диагностик структурированные сообщения с устойчивыми кодами с 3.x
bin/htmlphp CLI: версия, сборка, проверка каталога, извлечение строк
tools/build.php --check расхождение src/ и файлов сборки

Самое полезное здесь — lintDirectory(). Опечатка в шаблоне обычно обнаруживается в момент, когда страницу открыл читатель; линтер переносит эту ошибку в сборку. Второе по полезности — stats(): он показывает попадания в кэш и число подстановок, то есть отвечает на вопрос «почему эта страница рендерится 80 мс», не требуя профайлера.

Что смотреть На что указывает
cacheHit низкий включённый кэш работает впустую: шаблон меняется часто
substitutions растёт, durationMs тоже тяжёлые фильтры или большие циклы
loops десятки на страницу шаблон собирает данные, которые должен был собрать контроллер

Сравнение с альтернативами

Вариант Плюсы Минусы
Twig зрелость, экосистема, строгий синтаксис зависимость, свой кэш, отдельный язык шаблонов
Blade привычен для Laravel вне Laravel требует обвязки
Чистый PHP в шаблонах ничего не ставить экранирование вручную, дисциплина на совести разработчика
HTML.PHP один файл, автэкранирование, SSI и i18n рядом свой синтаксис, экосистема маленькая

Выбор честно сводится к вопросу «нужен ли отдельный слой между контроллером и HTML». Если команда уже умеет Twig — менять смысла мало. Если проект небольшой, а зависимости нежелательны, один файл решает задачу без инфраструктуры.

Подводные камни

  • Версия файла важна. php85/html.php требует PHP 8.5 и откажется работать на 8.3; универсальный вариант — php83/html.php.
  • Смена мажорной версии ничего не ломает, но проверять надо. 6.0.0 — это 5.0.0 без изменений в движке; обновление сводится к замене файла и сверке контрольной суммы.
  • Кэш рендера по умолчанию выключен. Включённый кэш с файловым драйвером требует записываемого каталога и, при cache.harden_directory, кладёт рядом .htaccess и index.html.
  • #exec по умолчанию запрещён. Это не недоработка: политика deny — сознательное решение. Включать её стоит только осознанно и с пониманием, кто пишет шаблоны.
  • Шаблоны, редактируемые пользователями, требуют урезанного режима. syntax.allow_raw_filter: false и syntax.expressions: false — не рекомендация, а необходимость.
  • Опечатка в конфигурации падает исключением. Это плюс, но при миграции со старой версии вылеты будут, пока ключи не приведены в порядок.
  • bin/htmlphp в чекауте подключает src/, а не файл сборки. Баннер версии напишет 8.3 edition даже на PHP 8.5 — это не ошибка.
  • Bundles нужно пересобирать. Если правится src/, файлы сборки отстают; в CI за этим следит tools/build.php --check.
  • Мимо каталога Packagist. Наличие пакета в репозитории не подтверждено, а команды composer require взяты из README.
  • Один файл — не значит «ничего не читать». 16 тысяч строк в одном файле удобно подключать, но неудобно ревьюить в диффе.

Чек-лист

  • [ ] Проверить версию PHP: 8.3+ для универсального файла, 8.5 для сборки 8.5.
  • [ ] Скачать файл релиза и сверить SHA-256 с заметками к релизу.
  • [ ] Запустить однострочную проверку экранирования.
  • [ ] Задать templates.paths и путь к каталогам перевода.
  • [ ] Решить, нужен ли кэш рендера, и если да — подготовить каталог.
  • [ ] Выключить syntax.expressions и allow_raw_filter, если шаблоны правят пользователи.
  • [ ] Проверить, что ssi.exec_mode остался deny.
  • [ ] Прогнать линтер шаблонов в CI: lintDirectory().
  • [ ] Записать версию и хеш файла в паспорт проекта.

FAQ

Нужен ли Composer?

Нет. Основной способ — скачать один файл и подключить его через require. Composer — вариант для проектов, где уже есть автозагрузка PSR-4.

Чем php83/html.php отличается от php85/html.php?

Первый работает на PHP 8.3, 8.4 и 8.5, второй требует 8.5 и использует его возможности. Размер отличается на 1 822 байта, набор возможностей — нет.

Это замена Twig?

Не замена, а альтернатива для случая «нужен минимальный слой без зависимостей». Twig даёт больше, но требует композера, своего кэша и отдельного языка шаблонов.

Безопасно ли отдавать шаблоны пользователям?

Только с syntax.allow_raw_filter: false и syntax.expressions: false. Ядро не вызывает eval() и не читает суперглобальные массивы, но выражения и сырой фильтр — это ровно те возможности, которые не нужны пользовательским шаблонам.

Что даёт версия 6.0.0, если движок не менялся?

Лицензионную ясность: MIT прописан в LICENSE, корневом composer.json, манифестах редакций, шапке плагина и всех README, а тест 16_release_contract падает, если они разойдутся.

Можно ли применить SSI к содержимому из базы?

Да, для этого есть processSsi($content). Директивы разбираются в PHP, поэтому веб-сервер роли не играет.

Итог

HTML.PHP 6.0.0 — библиотека с ясной границей: шаблоны, SSI и локализация в одном файле, без зависимостей и без выхода в сеть. Проверка на живом PHP 8.5 подтвердила главное, что здесь можно проверить быстро: оба файла релиза совпадают по контрольным суммам с заявленными, автэкранирование работает по умолчанию, арифметика в подстановках считает, фильтр чисел уважает локаль, а циклы и условия выдают ожидаемое. Ограничения тоже честные: свой синтаксис, маленькая экосистема, 16 тысяч строк в одном файле и набор директив SSI, который надо включать с головой.

Цитата: Хороший шаблонизатор не тот, который умеет больше, а тот, после которого не приходится объяснять, почему в HTML приехал пользовательский тег.

Связанные материалы

LLM Summary

  • Тема: HTML.PHP 6.0.0 — библиотека обработки шаблонов для PHP: шаблоны, Apache SSI и gettext-совместимая локализация без зависимостей.
  • Лицензия: MIT, прописана в LICENSE, корневом composer.json, манифестах, шапке плагина WordPress и всех шести README; расхождение ловит tests/cases/16_release_contract.test.php.
  • Движок: без изменений относительно 5.0.0 — ни одна конструкция шаблонов, ключ конфигурации, фильтр, команда CLI или метод API не менялись; переход — замена файла.
  • Файлы релиза (замерено 09.11.2026): html-php-6.0.0-php83.php 530 172 байта, SHA-256 7df928018f8f43f7e9d402e7a5ff40fed73f490f549b5a69e8e07afa1f1477b6; html-php-6.0.0-php85.php 531 994 байта, SHA-256 19c4e456842d9a34ab70e70a71b4dddd19b8449d459daa288a4c18f6da4d0274; оба совпали с заметками к релизу.
  • Прогон на PHP 8.5.0: экранирование Hello %{name} → Hello &lt;World&gt;; %{= done / total * 100 | round:1 }% → 14.8%; %{price|number:2} при локали ru → 1 234,50; _position в цикле; @If, @Set, диапазон (1..3)[1]; t('{0} files', [42]) → 42 files.
  • Безопасность: ядро не вызывает eval() и не читает $GLOBALS и суперглобальные массивы (проверяется тестом на уровне токенов); syntax.auto_escape: true и ssi.exec_mode: deny по умолчанию; для пользовательских шаблонов — syntax.allow_raw_filter: false, syntax.expressions: false.
  • Тесты по бейджу: 391 тест, 389 пройдено, 2 пропущено; PHPStan уровень 6; php-cs-fixer чист на 143 файлах.
  • Чего нет: нет сети и телеметрии, нет записи на диск без явной настройки, нет собственного кэша по умолчанию (cache.enabled: false).
  • Не проверялось: производительность под нагрузкой, работа #exec и #block, поведение WordPress-моста в живом сайте.
  • Ссылки: https://github.com/paulmann/HTML.PHP, https://github.com/paulmann/HTML.PHP/releases/tag/v6.0.0

Промт для агента (установка и настройка)

Готовый промт: скопируйте блок целиком. Шаги и команды взяты из AGENTS.md репозитория (он есть, проверено HTTP 200) и из заметок к релизу 6.0.0.

Задача: поставить HTML.PHP 6.0.0 в PHP-проект, включить экранирование и кэш, проверить работу шаблонов и локализации.

Материалы:
- репозиторий: https://github.com/paulmann/HTML.PHP
- AGENTS.md: https://github.com/paulmann/HTML.PHP/blob/main/AGENTS.md
- README: https://github.com/paulmann/HTML.PHP/blob/main/README.md
- релиз: https://github.com/paulmann/HTML.PHP/releases/tag/v6.0.0

Шаг 1. Проверить предусловия: php -v (нужно 8.3+; для сборки 8.5 — 8.5+), php -m
Шаг 2. Скачать файл релиза и сверить контрольную сумму:
curl -fsSL -o html.php https://github.com/paulmann/HTML.PHP/releases/download/v6.0.0/html-php-6.0.0-php83.php
sha256sum html.php    # 7df928018f8f43f7e9d402e7a5ff40fed73f490f549b5a69e8e07afa1f1477b6
Шаг 3. Проверить, что библиотека загружается и экранирует вывод:
php -r "require 'html.php'; echo HtmlPhp\Engine::create()->renderString('Hello %{name}!', ['name' => '<World>']), PHP_EOL;"
Ожидается: Hello &lt;World&gt;!
Шаг 4. Создать конфигурацию (массив в Engine::create или файл для CLI, который возвращает массив):
templates.paths, i18n.locale и i18n.paths, cache.enabled и cache.directory
Шаг 5. Убедиться, что защита на месте: syntax.auto_escape = true, ssi.exec_mode = deny; для шаблонов, которые правят пользователи, добавить syntax.allow_raw_filter = false и syntax.expressions = false
Шаг 6. Прогнать линтер шаблонов в CI: $engine->lintDirectory(<каталог шаблонов>)
Шаг 7. Снять stats() после первых запросов и записать значения для сравнения в будущем

Запрещено:
- включать ssi.exec_mode = allow без явного решения владельца проекта;
- подключать php85/html.php на PHP ниже 8.5: файл откажется работать;
- полагаться на старый файл 4.x без чтения docs/MIGRATION-4.x-TO-5.0.md;
- считать, что composer require paulmann/html-php работает всегда: наличие пакета на Packagist в репозитории не подтверждено, проверять;
- править src/ без пересборки файлов: расхождение ловит tools/build.php --check.

Что считать успехом: однострочная проверка выводит Hello &lt;World&gt;!, шаблон с циклом и условием рендерится, перевод через t() работает, stats() отдаёт значения, линтер шаблонов чист.