Pull to refresh

Fenom — yet another PHP template engine

Reading time 6 min
Views 44K
Доброго времени суток. Вышла первая стабильная версия нового шаблонизатора Fenom. Для нетерпеливых, Fenom — это легковесный (статистика прилагается), быстрый (бенчмарк прилагается), гибкий (API прилагается) шаблонизатор, который может стать удачной заменой шаблонизаторам Twig или Smarty.


Шаблонизатор Fenom является ярким примером того, как вопрос «интересно, а на что способно расширение tokenizer?» может привести к чему-то большему, чем пару скриптов в песочнице. Как и многие проекты, шаблонизатор начинался just-for-fun и был создан за один вечер. Первый вариант шаблонизатора занимал всего 900 строк кода, который парсил примитивные шаблоны. Для поддержания расширяемости в код шаблонизатора пришлось добавить еще 2500 строк кода.

Если кому интересно, я могу рассказать в отдельной статье все интересные моменты и нюансы, с которыми пришлось столкнуться при создании шаблонизатора.

Идеология



Когда стало ясно, что эксперимент вышел за рамки обычной игры в песочнице, появилось желание довести его до полноценного OpenSource проекта, который смог бы использоваться в highload проектах и составить конкуренцию популярным PHP шаблонизаторам. Шаблонизация в highload проектах очень сложная тема. Когда приходится иметь дело с сотнями тысяч user-space шаблонов у всех шаблонизаторов начинают появляться свои «тараканы». К моменту создания Fenom, мне удалось поработать с такими PHP шаблонизаторами как Smarty2, Smarty3, Quicky, Twig, Latte. Опыт работы с ними помог мне понять каким должен быть шаблонизатор «моей мечты» :)
При разработке шаблонизатора я старался придерживаться следующих требований:
  • Максимальная гибкость. Гибкость шаблонизатора должна быть достаточная, что бы добавить любую функциональность или же возможность изменить поведение шаблонизатора при необходимости. Это диктуется тем, что автор шаблонизатора может быть не в курсе всех потребностей в шаблонизации, поэтому должен дать возможность расширения шаблонизатора.
  • Доверяй, но проверяй. Безопасность шаблонизатора должна включать в себя не только защиту от различных инъекций, но и защиту от не правильного использования различных операторов самого шаблонизатора, которые могут привести к PHP ошибке при выполнении кода шаблона. Это особенно актуально когда к шаблонам имеют доступ обычные пользователи.
  • Чем быстрее, тем лучше. Производительность должна быть сопоставима со скоростью генерации шаблона написанного на чистом PHP коде. А при определенных условиях шаблонизатор должен выигрывать по скорости шаблоны на чистом PHP.
    Полагаю, во избежание лишних холиваров, этот пункт необходимо пояснить: Если дать задачу разработчику реализовать некоторый view на чистом PHP, то реализация будет скорее всего через ООП, не говоря уже о дублировании кода. Шаблонизатор же лишен всех предрассудков и с легкостью создает простыни валидного кода, оптимизированного под быстрое выполнение. Отличным примером является алгоритм наследования шаблонов.
  • Будь проще и люди к тебе потянутся. Шаблонизатор, как и любой другой проект, должен базироваться на принципе KISS. Код максимально компактен и понятен, ООП без фанатизма, везде прослеживаемая логика. Это очень важно для любого проекта, так как чем больше кода в проекте тем сложнее его поддерживать, отлаживать, тестировать и привлекать сторонних разработчиков в такой проект. Это одна из причин почему в Fenom вообще не используются регулярные выражения для разбора шаблонов.
  • Старый друг лучше новых двух. Лучше перенять общепринятый простой синтаксис существующих шаблонизаторов, чем внедрять новый. В Fenom за основу взят Smarty-like синтаксис шаблонов, так как там всего один вид тега обрамляемый в фигурные скобки и этот синтаксис проще преобразовать в токены.
  • Не жрать много. Алгоритмы Fenom стараются по максимуму сэкономить ресурсы машины — как CPU, так и оперативной памяти. Все сложные вычисления шаблонизатор старается произвести на этапе компиляции шаблона, чтобы выполнение было максимально быстрым и не ресурсоемким. В следствии этого шаблонизатор имеет возможность отдавать данные потоком (методы display и export), не накапливая в памяти.
  • Не свинячить. Шаблоны Fenom — объекты, которые освободят память при отсутствии на них ссылок, в то время как у Twig — классы, Smarty — функции, которые при всем желании не удалить из памяти, что может добавлять трудности при обработке большого числа шаблонов. Кроме того итоговый PHP код шаблона не должен быть большим, так как чем больше шаблон, тем больше оп-кода, тем меньше памяти в оп-кешерах.


Производительность



Набор тестов я позаимствовал у товарища VEG из статьи Smarty vs. Twig: производительность, где Twig показал себя не с лучшей стороны. С тех пор прошло не мало времени так что я решил повторить, тесты приведенные в статье, с дополнительным соперником — Fenom. Действующие лица: Smarty версии 3.1.13, Twig версии 1.13.0 + расширение twig.so, Fenom версии 1.0.3. Очень хотел протестировать Volt из нативного фреймворка Phalcon, но, увы, на первом же тесте он ушел в бесконечный цикл, поэтому его пришлось дисквалифицировать.

Каждый тест запускался в 3х разных режимах:
  • Холодный запуск — шаблон не скомпилирован и, следовательно, не загружен в шаблонизатор. Очень редкий случай когда шаблон запускает впервые. Тем не менее если произошел сброс кеша то всплеск компиляций шаблонов может загубить систему. Показатель скоростей компиляции, загрузки и исполненная шаблонов.
  • Штатный запуск — шаблон уже скомпилирован на файловую систему, но не загружен в шаблонизатор. Довольно частый случай когда шаблон уже выполнялся ранее, поэтому уже имеет кеш собственной компиляции на файловой системе. Показатель актуален если шаблон используется всего лишь один раз за выполнение скрипта. Значение характеризует скорости загрузки и исполнения шаблонов.
  • Горячий запуск — шаблон скомпилирован и даже уже загружен в шаблонизатор. Означает, что шаблон используется более чем 1 раз за вызов скрипта. Показатель скорости исполнения шаблона.


Тесты выполнялись на 2,6 ГГц Intel Core i7, 8 ГБ 1600 МГц DDR3, MacOS 10.8.4, PHP 5.4.15 из-под CLI. Ubuntu 12.04 показала тоже соотношение в результатах. PHP запускалось с флагом -n, что отключает все внешние расширения. С twig.so тестировал отдельно. Результат брался со второго прогона тестов, когда ОС уже закешировала файлы тестов и шаблонизаторов. Op-кешеры, конечно же, отключены как и все другие расширения.

Итак, первым тестом идет вывод большого количества переменных. По сути, в этом тесте есть смысл так как все шаблоны состоят из вывода переменных в шаблон.
Вывод большого количества переменных:
Шаблонизаторы Холодный запуск Штатный запуск Горячий запуск
Smarty3 4.3394 sec, 15.2 MiB 0.0239 sec, 9.2 MiB 0.0015 sec, 9.2 MiB
Twig 1.9618 sec, 68.9 MiB 0.0341 sec, 17.0 MiB 0.0013 sec, 17.0 MiB
Fenom 0.3432 sec, 8.9 MiB 0.0157 sec, 6.6 MiB 0.0011 sec, 6.6 MiB

В первом же тесте Twig выделил себя — тесты упали из-за превышения ограничения памяти (по умолчанию 32MB).

Второй тест представляет из себя итерацию большого массива, что тоже частый случай в шаблонах.
Итерация большого массива:
Шаблонизаторы Холодный запуск Штатный запуск Горячий запуск
Smarty3 0.0223 sec, 5.8 MiB 0.0036 sec, 3.1 MiB 0.0024 sec, 3.1 MiB
Twig 0.0300 sec, 4.0 MiB 0.0174 sec, 2.7 MiB 0.0166 sec, 2.7 MiB
Twig + extension 0.0225 sec, 4.7 MiB 0.0064 sec, 3.2 MiB 0.0060 sec, 3.5 MiB
Fenom 0.0080 sec, 3.1 MiB 0.0022 sec, 2.5 MiB 0.0017 sec, 2.5 MiB


Третий тест — наследование большого числа шаблонов. Этот тест я считаю более надуманным ибо более 2-3х уровней наследований хватает за глаза, тем не менее в тесте осталось 100 уровневое наследование. Не смотря на всю надуманность тест дает общее представление о производительности наследования шаблонов.
Наследование большого числа шаблонов:

Шаблонизаторы Холодный запуск Штатный запуск Горячий запуск
Smarty3 0.4165 sec, 10.1 MiB 0.0008 sec, 3.1 MiB 0.0001 sec, 3.1 MiB
Twig 0.3626 sec, 11.2 MiB 0.0252 sec, 6.5 MiB 0.0021 sec, 6.5 MiB
Fenom 0.0569 sec, 3.2 MiB 0.0005 sec, 2.5 MiB 0.0000 sec, 2.5 MiB

Twig и тут выделился, тест упал из-за превышения допустимой вложенности, в данном случае рекурсии. Ошибку бросило расширение xDebug, которое в последствии для тестов отключил.

Итог
Очевидным победителем стал Fenom, который в значительной степени опередил Smarty и Twig как по скорости, так и по экономии ресурсов. На втором месте Smarty, бронзу получает Twig.

Что же под капотом?


Ниже приведена таблица «жирности» кода:
Шаблонизатора Количество файлов Количество классов Количество строк кода
Smarty (3.1.13) 320 190 55095
Twig (1.13.0) 162 131 13908
Fenom (1.0.4) 9 13 3967


Отмечу что:
  • Smarty использует BISON генератор парсеров, который наплодил тучу методов. Что ж, с точки зрения производительности он не плохо себя показал. Тем не менее полученный код довольно не опрятен.
  • Twig плотно сидит на регулярных выражениях, но я видел и хуже. Код приятен, читабелен, но, можете считать меня Маркусом, переполнен сущностями.
  • Fenom во всю использует базовое расширение tokenizer (который так же построен при помощи BISON).


Где взять?


Репозиторий: github.com/bzick/fenom
Packagist.org: packagist.org/packages/bzick/fenom
Composer: "fenom/fenom": "1.*"
Документация: github.com/bzick/fenom/blob/master/docs/readme.md
Баг трекер: github.com/bzick/fenom/issues

Что дальше?



На данный момент Fenom имеет уже досточно большой набор возможностей. Но в планах реализовать тег {parent} для наследования шаблонов, добавить операторы in и is, а так же дописать и перевести документацию на английский язык. Буду рад если найдутся желающие помочь.

P.S.
Шаблонизатор все еще продолжает развиваться и принимает все конструктивные предложения и критику, которые вы можете написать в комментариях ;)
P.P.S.
Много просьб было добавить полноценное автоэкранирование, а так же raw фильтр. В итоге версия 1.0.7 уже имеет всю необходимую функциональность для работы с автоматическим экранированием.
Tags:
Hubs:
+69
Comments 183
Comments Comments 183

Articles