Программист, Web-разработчик
0,0
рейтинг
4 июля 2013 в 01:52

Разработка → Fenom — yet another PHP template engine

PHP*
Доброго времени суток. Вышла первая стабильная версия нового шаблонизатора 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 уже имеет всю необходимую функциональность для работы с автоматическим экранированием.
Иван @Aco
карма
35,0
рейтинг 0,0
Программист, Web-разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (183)

  • +7
    Давно уже не использовал шаблонизаторы, но подход к делу очень порадовал. Для успеха осталось сделать мегакрасивый сайт на нескольких языках, с кучей примеров и бенчмарком и опубликоваться во всяких nettuts и phpmaster.
  • +9
    Если кому интересно, я могу рассказать в отдельной статье все интересные моменты и нюансы, с которыми пришлось столкнуться при создании шаблонизатора.

    Интересно! Также думаю если кто-то из русского сообщества захочет разобраться со внутренностями, отправить патч и тд, будет полезно почитать об этом
  • +4
    Хочу бенчмарка Fenom vs native PHP с реализацией типа ob_start.
    • 0
      Я тоже планировал это сделать, но не смог нащупать ту самую границу когда шаблон становится не нативным. Например для наследования нативно не получится написать шаблон, нужны классы, методы и тд… микро шаблонизатор, что-ли.
      В итоге получалась какая-то копипаста из шаблонов Fenom, там и так все просто до непреличия
      Код шаблона Fenom
      <?php 
      /** Fenom template 'foreach/smarty.tpl' compiled at 2013-07-04 01:43:57 */
      return new Fenom\Render($fenom, function ($tpl) {
      ?><h1>Вывод 10 полей из 1000 элементов в цикле</h1>
      <?php
      /* foreach/smarty.tpl:2: {foreach $array as $item} */
        if($tpl["array"]) {  foreach($tpl["array"] as $tpl["item"]) {  ?>
      
      <?php
      /* foreach/smarty.tpl:3: {$item.id} */
       echo $tpl["item"]["id"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.title} */
       echo $tpl["item"]["title"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var1} */
       echo $tpl["item"]["var1"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var2} */
       echo $tpl["item"]["var2"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var3} */
       echo $tpl["item"]["var3"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var4} */
       echo $tpl["item"]["var4"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var5} */
       echo $tpl["item"]["var5"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var6} */
       echo $tpl["item"]["var6"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var5} */
       echo $tpl["item"]["var5"]; ?>
       <?php
      /* foreach/smarty.tpl:3: {$item.var6} */
       echo $tpl["item"]["var6"]; ?>
       
      <?php
      /* foreach/smarty.tpl:4: {/foreach} */
         } } ?>
      <?php
      }, array (
        'options' => 0,
        'provider' => false,
        'name' => 'foreach/smarty.tpl',
        'base_name' => 'foreach/smarty.tpl',
        'time' => 1369074113,
        'depends' => 
        array (
        ),
      ));
      
      

      • +2
        Я, прежде всего, про то, что при применении нативного PHP в качестве шаблонов реализация обычно без кеша и примерно такая:

        // Путь до папки с шаблонами
        define('VIEWS_BASEDIR', dirname(__FILE__).'/views/');
        
        class View {
            // получить отренедеренный шаблон с параметрами $params
            function fetchPartial($template, $params = array()){
                extract($params);
                ob_start();
                include VIEWS_BASEDIR.$template.'.php';
                return ob_get_clean();
            }
        
            // вывести отренедеренный шаблон с параметрами $params
            function renderPartial($template, $params = array()){
                echo $this->fetchPartial($template, $params);
            }
        
            // получить отренедеренный в переменную $content layout-а
            // шаблон с параметрами $params
            function fetch($template, $params = array()){
                $content = $this->fetchPartial($template, $params);
                return $this->fetchPartial('layout', array('content' => $content));
            }
        
            // вывести отренедеренный в переменную $content layout-а
            // шаблон с параметрами $params    
            function render($template, $params = array()){
                echo $this->fetch($template, $params);
            }
        }
        
        • –1
          Так нативные шаблоны ничем не отличаюся от скомпилированных, если правильно компилировать. Возможно даже некоторые моменты шаблонизатор лучше заоптимизирует, но по хорошему явно не хуже. А проиграет только на этапе компиляции
          • 0
            Как раз интересует «лучше заоптимизирует» сравнить.
          • +1
            Что должно случиться, чтобы компилятор шаблона выпек нативный php-файл, который лучше оптимизирован, чем нативный php-файл, написанный вручную?
            • 0
              Голова разработчика может случиться, которая не даст просто взять и лапшойнаписать PHP код, ведь нужны классы, интерфейсы, абстакция и тд. Ни кто уже не пишет спагетти-код кроме шаблонизаторов. А такую вещь как наследование шаблонов на чистом спагетти PHP не получится реализовать.
              • 0
                Так нативные шаблоны ничем не отличаюся от скомпилированных, если правильно компилировать. Возможно даже некоторые моменты шаблонизатор лучше заоптимизирует, но по хорошему явно не хуже.
                Я всего лишь хотел конкретно увидеть пример, когда нативный php-шаблон медленнее скомпилированного. Вариант, что нативный php-шаблон написан невменяемым психопатом, а компилируемый шаблон написан умницей-программистом я в расчет предлагаю не брать.
    • +1
      Сегодня обнаружил одну неприятную особенность Twig — какая-то жуткая потеря производительности при использовании вложенных массивов и проверки нахождения ключей в них в цикле… начал смотреть как ту же ситуацию обработают другие шаблонизаторы, вот и про Fenom вспомнил. Получилось как-то так

      Цифра — round(1 / (microtime(true) — $microtimeStart));

      Native PHP 3000/sec
      Fenom 1700/sec
      Smarty 1000/sec
      Twig 100/sec
      • 0
        да, это одна из причин почему я взялся за шаблонизатор. хотя странно что феном почти в 2 раза сдает нативному PHP ведь он конвертит в нативный PHP, а с какими параметрами запускали?
        • 0
          Вот тут я выложил файл с данными и примеры шаблонов
          groups.google.com/forum/#!topic/twig-users/R4FcQReXPQQ

          Запускал просто на сконфигурированном почти по дефолту сервере — PHP 5.5 FPM/Nginx/Debian/Amazon Micro Instance
  • +1
    Интересно было б сравнить с phalcon'овским шаблонизатором
    • 0
      Очень хотел протестировать Volt из нативного фреймворка Phalcon, но, увы, на первом же тесте он ушел в бесконечный цикл, поэтому его пришлось дисквалифицировать.
      • 0
        Надо написать парням из Phalcon, мне кажется они вполне смогут помочь
        • 0
          а руки тестеров уже не учитываются?
  • –3
    Какую проблему может решить Ваша библиотека, которую не могут решить подобные другие?
    • +4
      Я очень рад, что с моим мнением… даже не мнением, а вопросом… не согласны, отрицательно на него реагируют. Но за пояснения я был бы признателен.
      • +3
        Библиотека имеет дугой подход к шаблонизации, упрощенную иерархию, отенциал, легко расширяется, экономит ресурсы машины, есть свои идеи и минимальный набор для шаблонизации, что удобно для маленьких проектов. А иногда проекты создаются из-за того что это просто интересно и увлекательно создавать. Всегда приятно иметь выбор.
    • +1
      скорость? меньшее пожирание памяти/cpu?
      • +2
        Вы сталкивались с проблемой нехватики памяти/cpu во время использования шаблонизатора?
        • +1
          Да, сталкивался чаще чем хотелось. Я бы не заморачивался с этим если бы не столкнулся с проблемой. Для highload проекта любой магабайт опретивы и любой процент cpu важен, так как объемы данных занчительные, а производительность не должна падать.
          • +1
            Как решали проблемы с которыми сталкивались? Ведь ещё не используете свою реализацию на существующих хайлоад продуктах.
            • 0
              Итераторами и ограничением количества данных в шаблон, отчего пришлось отказать от некоторых плюшек.
              • 0
                Итераторами

                А вот это интересно, как именно вы применили итераторы к шаблонам? Можете привести пример?
                • +1
                  Давайте на примере mongodb, так как у расширения очень удобный курсор-итератор, который в реальном времени подгружает результат запроса из бд во время итерации. Суть такова, в шаблон отдается курсоры-итераторы на начальном состоянии, которые не начали прохождение по результату, тем самым не потребляют память. В шаблоне идет итерации по курсорам с отрисовкой в output. Таким образом, насколько большой результат не был, курсор не будет превышать определенного размера. Вот тут главное что бы шаблонизатор не копил у себя output иначе это все бессмыслено. Надеюсь суть мне удалось передать.
                  • 0
                    Ага, понятно. Я, собственно, подумал что вы с проблемой внутренней буферизации как-то боролись с помощью итераторов, вот и заинтересовался.
          • 0
            На чем остановились в результате? В сторону blitz смотрели?
            • 0
              Да, смотрели. Он не подходит по возможностям. Я в бенчмарк добавлю, без наследования шаблонов, правда.
  • +10
    С blitz сознательно не сравнивали? :)
    • –1
      Честно, я даже не знал что он еще жив)
      • 0
        ясно, шутку не оценили
  • +1
    И ещё… у меня создалось впечатление, что Вы используете token_get_all для токенезирования своего синтаксиса. В принципе ничего такого… но меня это беспокоит.
    • +1
      Поясните ваше беспокойство
      • 0
        Беспокойство в том, что token_get_all предназначен для языка PHP. А не для Вашего языка. Ведь таким макаром можно пойти дальше: начинать разбираться этой функций какой-нибудь код какого-нибудь другого языка.

        Хотя, надо отметить, у этого подхода есть преимущества.
        • 0
          Здесь есть как плюсы так и минусы. Плюсы в том что на токены разбирает сам движок PHP, что получается очень быстро. Минус в том что оочень много мусорных токенов на выходе, приходится зачищать. Но у меня, как говорится, «палец на курке» — есть наработка токенайзера, который можетзаменить token_get_all если он станет не годным.
          • 0
            Я бы предложил дополнительные абстракцию вокруг token_get_all. Но, так как Вы позиционируюте его как очень быстрый, не буду: ).
  • 0
    Надо будет попробовать вплить в symfony2.
    • 0
      Нужно будет писать дополнения (генерация ссылок например).
  • 0
    Спасибо, интересный проект.
    Вопросы:
    1. Вы планируете поддержку и развитие, или это временное увлечение/хобби/текущий проект?
    2. Планируется ли добавление кэширования в шаблонизатор?
    • 0
      1. Да, планирую.
      2. Какого рода кеширование?
      • 0
        > 2. Какого рода кеширование?
        Когда компилированный шаблон уже есть и мы имеем native PHP до момента необходимости перекомпиляции шаблона.
        • +1
          Так оно и есть, пример скомпиленного шаблони (тест с итерациями массива).
          • 0
            т.е. шаблон повторно не перекомпилируется каждый раз?
            • 0
              Нет, Fenom кеширует PHP код шаблона на файловой системе. Только сейчас заметил, что об это нет ни слова в документации…
              • 0
                А по какому принципу происходит перекомпиляция?
                • 0
                  Если включен флаг Fenom::FORCE_COMPILE то всегда перекомпилирует, это для отладки. Если включен флаг Fenom:: AUTO_RELOAD то шаблонизатор перед использованием шаблона проверяет mtime оригинального шаблона и его кеша, если не совпадают — перекомпилирует. В других случая компиляция вызывается только если нет кеша шаблона.
                  • 0
                    А если данные изменились после компиляции спасет только ручной сброс файлов кэша? По времени очистки нет?
                    • 0
                      Данные подставляются в кеше, в самом коде PHP, как в примере выше. Мне кажется, что вы имеете ввиду кеш результата работы шаблона?
                      • 0
                        >Мне кажется, что вы имеете ввиду кеш результата работы шаблона?
                        Совершенно верно
                        • 0
                          Да, задавался я таким вопросом и вот что сказал мой опыт:
                          1. Кешировать результат отрисовки всего лучше у nginx или заголовком 304 Not Modified.
                          2. Если нужно частичное в шаблоне кеширование, то можно ввести тег {cache id="..."}{/cache} который будет по id сохранять и забирать из кеша данные. Появляется вопрос о настройки кешера для тега. Реализация будет отдельным пакетом, который добавляет тег {cache}.
                          3. Если нужно закешировать результат шаблона целиком то можно переопределить метод display + тег {include} на котором базируются другие методы и там завести необходимые ключи для кеша. Реализация будет отдельным пакетом, который предоставляет trait и/или класс переопределяющий метод display, адаптируя его для кеширования.

                          Все варианты выполнимы и могут быть реализованы как сторонний пакет. Сам пока не остановился на определенном варианте. Но во всех случая ключ для кеша указывается в ручную.
                          • 0
                            С ручной установкой ключа кэша все понятно, это и логично. А вот по поводу частичного (блочного) или полного кэширования – нужно и то и другое.
                            Кэширование на стороне nginx – это хорошо, но не всегда удобно. Управлять таким кэшем довольно проблематично для небольших проектов.
                            Спасибо за ответы, я понял, что на данный момент есть в шаблонизаторе, буду следить за развитием.
  • 0
    Все выглядит весьма не плохо, но есть еще над чем работать особенно по коду, первое что бросается в глаза это через чур излишняя вложенность ветвлений… т.к. такое встречается практически повсеместно в коде, для примера:

       public static function tagInclude(Tokenizer $tokens, Template $tpl) {
           if($p) { // if we have additionally variables
                if($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
                    $inc = $tpl->getStorage()->compile($name, false);
                    $tpl->addDepend($inc);
                    return '$_tpl = (array)$tpl; $tpl->exchangeArray('.self::toArray($p).'+$_tpl); ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
                } else {
                    return '$tpl->getStorage()->getTemplate('.$cname.')->display('.self::toArray($p).'+(array)$tpl);';
                }
            } else {
                if($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
                    $inc = $tpl->getStorage()->compile($name, false);
                    $tpl->addDepend($inc);
                    return '$_tpl = (array)$tpl; ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
                } else {
                    return '$tpl->getStorage()->getTemplate('.$cname.')->display((array)$tpl);';
                }
            }
      }
    

    Ведь если чуть чуть подумать, то можно все записать весьма более читаемо и избавиться от не нужной вложенности:

        public static function tagInclude(Tokenizer $tokens, Template $tpl) {
            if($p) { // if we have additionally variables
                if($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
                    $inc = $tpl->getStorage()->compile($name, false);
                    $tpl->addDepend($inc);
                    
                    return '$_tpl = (array)$tpl; $tpl->exchangeArray('.self::toArray($p).'+$_tpl); ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
                }
    
                return '$tpl->getStorage()->getTemplate('.$cname.')->display('.self::toArray($p).'+(array)$tpl);';
            } elseif($name && ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE)) { // if FORCE_INCLUDE enabled and template name known
                $inc = $tpl->getStorage()->compile($name, false);
                $tpl->addDepend($inc);
                
                return '$_tpl = (array)$tpl; ?>'.$inc->_body.'<?php $tpl->exchangeArray($_tpl); unset($_tpl);';
            }
    
            return '$tpl->getStorage()->getTemplate('.$cname.')->display((array)$tpl);';
        }
    

    А так меня весьма радуют цифры тестов и думаю идею развивать стоит и дальше. Сделал форк на днях постараюсь привести код в божеский вид.
    • +1
      Тоже заметил сложную структуру методов, читается сложно. Там даже можно найти goto.
      • +1
        Вы не поверитие, это goto заменило 80 строк кода, один класс и 2 блока с try {} catch{}. Так вышло что тут было удобнее использовать goto, как бы не хотелось
        • +3
          Верю… Верю, что goto может уменьшить количество строк. Но так же верю, что тут можно избежать использование goto без особых накладных расходов на строчки или производительность.
          • 0
            Зачем? Оно такой же инструмент, если пользоваться умеренно.
            • 0
              Согласен. я всегда использользую goto в «области»
              mark: {
                  // ...
                  goto mark;
                  // ...
              }
              

              очеь читабельно и ясно
        • +5
          Но вы ведь знаете, что бывает с теми, кто пользуется goto
          image
          • 0
            Даа, с трепером жду дино)
            • +2
              Вы с трепером поосторожнее
    • 0
      Согласен, есть небольшие усложнения. Был бы очень рад если кто-нибудь сделал код-ревью, результатом которого issue со всеми ;)
      • +1
        Лучше pull-request.
  • 0
    Переходить со Smarty — одно удовольствие. Пожалуй, попробую в тестовом проекте.
  • 0
    Посмотрел список модификаторов — бросилось в глаза отсутствие фильтра с именем типа raw или safe. Так же не нашел такой опции в template settings.
    Считаю, что отсутствие данной функциональности делает его непригодным к использованию в самых типичных случаях использования шаблонизатора.
    • 0
      Полагаю в имеете ввиду Fenom::AUTO_ESCAPE автоэкранирование переменных? Да, в документации, по ошибке, пропущена данная опция, исправлю. На счет модификатора raw, пока не уверен как работает в других шаблонизаторах, но тут за него отлично сойдет модификатор unescape
      • 0
        Нет, не сойдет.

        php > $fenom = Fenom::factory('.', '/tmp', Fenom::AUTO_ESCAPE);
        php > echo ($fenom->compileCode('Hello {$name|unescape}!')->_body);
        
        Hello <?php
        /* Runtime compile:1: {$name|unescape} */
         echo htmlspecialchars(call_user_func($tpl->getStorage()->getModifier("unescape"), $tpl["name"]), ENT_COMPAT, 'UTF-8'); ?>
        


        1. Сначала применится unescape, потом htmlspecialchars. Т.е. вообще метод не рабочий.
        2. При raw ни то ни другое не должно применяться.
        3. Если убрать флаг Fenom::AUTO_ESCAPE, то unescape будет все равно применяться и портить строку.

        По сему видно, что вы не представляете, для чего нужны шаблонизаторы. Подсказка: не для забегов на перегонки.
        • 0
          Я полностью представляю для чего нужны шаблоны, просто я всегда знаю какие у меня данные. Fenom::AUTO_ESCAPE был добавлен только вчера, и, конечно, raw я еще не успел добавить.

          Однако, в приведеном вами коде я заметил багу — модификатор должен был вставится как есть то есть
          echo htmlspecialchars(\Fenom\Modifier::unescape($tpl["name"]), ENT_COMPAT, 'UTF-8');
          

          Спасибо!
          • +6
            Когда вы возьметесь за реализацию raw, вы поймете, что не получится просто так присобачить к строке состояние «экранировать не нужно». Вы сделаете для каждого выражения {$name|unescape} объект и unescape будет его методом. Потом вы увидите, что необъявленные переменные у вас выдают нотисы, начнете проверять есть ли переменная, прежде чем её выводить. Пройдет еще несколько таких итераций, прежде чем вы действительно поймете, чем должен заниматься шаблонизатор. И не факт, что уже на середине пути ваш шаблонизатор не станет медленне твига.

            А пока да, у вас быстрый типа шаблонизатор, который ничего не умеет.
            • –2
              Я ничего не буду навязывать вам. Просто значит этот шаблонизатор не для Вас.
            • +1
              Добавил raw и autoescape. Довольно быстро и легко, с проблемами которые Вы описали не столкнулся. Про проверку переменной это отдельная история, переменные у меня берутся через один код — Fenom\Template::parseVar() добвить туда тернарник не проблема, задача пока висит из-за написании тестов.

              Мне кажется Вы слегка нагнетаете, если архитектура проекта проста и ясна, то добавить хоть самый изврат — не проблема.
              • 0
                Кажется, у вас инъекция. Не благодарите.
              • +1
                Битвин, почему у raw такой синтаксис? Везде {$var|up|raw}, у вас {raw "{$var|up}"}.
                • 0
                  Я изначально считал raw, как модификатор, не лучшим вариантом. Неоднозначность ситуции {«Text» ~ $var|up|raw ~ $value|low} или схожих может привести к не пониманию и путанице. Так же модификатор не применить к inline или блоковой функции. Raw, по сути, упаравление потоком, делать через модификатор я считаю, мягко говоря, не корректно, так как модификатор меняет значение переменной непосредственно, а не управляет шаблонизатором. В предложенном мной варианте явно указано что raw применяется к тегу вцелом.
                  • 0
                    "Text" ~ $var|up|raw ~ $value|low

                    Вполне себе понятно, если знать приоритеты операторов и применения фильтров.
                    А если их не помнить (как я например), то всегда можно использовать скобки:
                    "Text" ~ ($var|up|raw) ~ ($value|low)

                    Тут уже ни у кого, надеюсь, не должно быть вопросов.

                    А для управления блоками raw в тех же Jinja/Django templates/Twig сделан не только фильтром, но и блочным тегом (правда называется verbatim).
                    • 0
                      Вас тоже этот вид raw, через модификатор, ввело в заблуждение.
                      Дело в том, что у twig не важно на какую переменную применен модификатор raw — ожидаемого воздействия не будет. При включенном экранировании, для тегов
                      {{ "data: " ~ a|upper|raw ~ b|lower }}
                      {{ "data: " ~ a|upper|raw ~ b|lower|raw }}
                      {{ "data: " ~ (a|upper|raw ~ b|lower)|raw }}
                      

                      результат будет один:
                      data: &lt;A&gt;A&lt;/A&gt;&lt;b&gt;b&lt;/b&gt;
                      
                      модификатор игнорируется.
                      Только
                      {{ ("data: " ~ a|upper|raw ~ b|lower)|raw }}
                      

                      будет иметь нужное нам значение, что очень похоже на мой реализованный вариант. Я просто предложил более прозрачный вариант, воздействие может быть только на тег целиком, поэтому пусть тег и указывает тип воздействия.

                      {verbatim} не то другое, это как {literal} в Smarty или {ignore} в Fenom — игнорирование тегов шаблонизатора.
                      • 0
                        Да, с raw запамятовал:
                        … in an environment with automatic escaping enabled this variable will not be escaped if raw is the last filter applied to it

                        А вместо verbatim:
                        {% autoescape false %}
                        {{ var }}
                        {% endautoescape %}
      • 0
        На счет модификатора raw, пока не уверен как работает в других шаблонизаторах, но тут за него отлично сойдет модификатор unescape
        Посмотрите.
        Сейчас автоматическое автоэкранирование — мейнстрим. Как может без него шаблонизатор обходиться — загадка.
        И я удивлен, что вы начали что-то писать, даже не зная, как это работает (не посмотрев другие шаблонизаторы). Это велосипед 90 уровня.
        • 0
          Не стоит удивляться. Я, конечно, был в курсе об автоэкранировании, но работая в highload проектах, данные подготавливаешь заранее потому что производительнее заэкранировать один раз данные перед сохранением в базу, чем экранировать каждый просмотр. Если сделать холодный расчет: 0.1ms занимает экранирование, допустим элементов отображается 50 на страницу, есть заголовок, описание и еще 3 текстовых поля у каждого элемента. Это 0.1 * 50 * 5 = 25ms, итого +25ms ко времени загрузки страницы, что довольно существенно для highload. Тем не менее я добавил экранирование, но по умолчанию эта опция выключена.
          • 0
            Количество стартапов и бизнес-приложений явно на порядки количества хайлоада.
            Ваш шаблонизатор заточен под хайлоад? А тесты синтетические.
            • 0
              Шаблонизатор только-только и печи и сейчас не пертендует везде на полную замену популярных шаблонизаторов. Все приходит со временем. Со временем я добавлю более боевые тесты и больше функциональности. А пока я приглашаю поучавствовать в развити проекта.
              • 0
                А теперь посмотрите на второе предложение в статье (о стабильной версии). Оно противоречит тому, что мы сейчас сказали.
                • 0
                  Шаблонизатор стабилен, покрыт тестами. В коментрарии я предлагаю присоеденится и наростить необходимую и недостающую фугкциональность.
  • +7
    > Op-кешеры, конечно же, отключены

    … и в результате тест носит исключительно академический характер: продакшена без опкод-кэшера не бывает. Впрочем, тесты и сами по себе вымученные из одного места (это, конечно, не к вам претензия) — ну кого волнует, что будет при 100500 переменных?

    Тест, хоть сколь-либо пытающийся приблизиться к реальному миру, есть у Рыбака (blitz benchmarks) — туда бы только наследование добавить.
    • 0
      Оп-кешер, только кеширует оп код, но не ускоряет его. Прирост может быть только за счет ускорения include/require. Не исключено, что на холодный старт это могло повлиять, поэтому я добавлю в доку тесты с включенным APC, пока просто времени не было так как там надо все делать изолировано друг от друга, что бы один тест не повлиял на результат другого.

      Согласен тесты довольно синтетические, но общее представление они дают.
      • +3
        zend opcache, включенный в поставку 5.5, кстати, немного ускоряет, но это непринципиально. Главное — синтетические тесты запросто покажут 500%-ю разницу там, где на реальных задачах — 1%.
        • 0
          да, самое сложное найти те самые реальные задачи, которые одинаково для всех шаблонизаторов можно реализовать. Тем не менее, по мере развития проекта я добавлю больше тестов и бенчмарков.
        • 0
          Все сложные конструкции реализуются на мелких конструкциях (вывод переменной, foreach), если мелкие конструкции тормозят то и сложные, следовательно, тоже будут тормозить.
          • –1
            а ты сделай и увидишь сам разницу, теоретик
          • +1
            Во-первых, далеко не факт: легко себе представить два алгоритма O(n^2) и O(log n) таких, что первый будет эффективнее на малых n.

            Во-вторых, правильный тест, а не измеритель скорости сферического коня в вакууме, пишется под конкретную задачу. Для одной из моих задач, например, критически важно, чтобы шаблон:

            — абстрагировал от системы типов php: одни и те же способы доступа должны равноценно работать для массивов, объектов, итераторов,
            — ни в коем случае не мог породить даже notice, не говоря уже о fatal error.

            Этим требованиям Twig удовлетворяет, а Smarty и ваше решение — нет. А это достаточно ресурсоемкая задача — именно эту часть (поиск значения по пути) оптимизирует twig extension.
            • 0
              " те же способы доступа должны равноценно работать для массивов, объектов"… " достаточно ресурсоемкая задача", а не проще контролировать входные данные в шаблон? Шаблонизатор у меня зародился по одной простой причине, я всегда уверен какие данные отдала система в шаблон и мне не нужен огород из проверок. Поискав по интернету, таковых шаблонизаторов (которые не считают себя умнее разработчика) я не нашел… поэтому взялся за молоток клавиатуру, но азарт разработки поглатил меня и вышло нечто большее чем примитивный шаблонизатор. Тем неменее идею я оставил: ваши данные — ваша ответвенность.
              • 0
                Поддерживаю. Все же массив и объект — разные вещи, и я никогда не понимал прелести этой твиговской фишки, когда абсолютно одинаковый синтаксис используется и для доступа к элементам массива, и для доступа к методам/свойствам объекта. Ну хочешь ты в шаблонах исключительно с массивами работать — преобразуй явно объекты в массивы перед передачей в шаблонизатор, и дело с концом. И незачем напрасно ресурсы шаблонизатора тратить на лишние угадывания
  • +5
    > Op-кешеры, конечно же, отключены как и все другие расширения.
    Их, конечно же, не надо было отключать. На этом можно остановиться, тесты не несут никакого смысла.
    • 0
      Смотрите комментарий выше
  • +2
    >> Очень хотел протестировать Volt из нативного фреймворка Phalcon, но, увы, на первом же тесте он ушел в бесконечный цикл, поэтому его пришлось дисквалифицировать.

    Создайте, пожалуйста, баг репор с описанием проблемы и путями ее возпроизведение. Буду вам признателен. Создать баг репорт можно здесь: github.com/phalcon/cphalcon/issues/new
    • +2
      Да, занимаюсь этим, в течении дня зарепортю. Дополнительно проверяю не кривые-ли это у меня руки)
      • 0
        спасибо ;)
  • +1
    > Если кому интересно, я могу рассказать в отдельной статье все интересные моменты и нюансы, с которыми пришлось столкнуться при создании шаблонизатора.

    Да, расскажите, было бы интересно.
  • 0
    Несколько раз пытался переходить на шаблонизаторы, но каждый раз возвращался к нативному PHP.
    Буду благодарен за доводы в пользу шаблонизатора: зачем?
    • 0
      Шо, опять? ©
      • +2
        Если всё уже всем понятно и разложено по полочкам, то просто дайте ссылку на текст, который меня просветит. Спасибо.
        • –2
          Ссылку вам ниже дали. Но я о другом: этот вопрос — типично холиварный, сродни K&R vs. Allman indent style, или там git vs. mercurial. Нечего тут обсуждать, все равно каждый останется при своем мнении.
          • +1
            Достаточно легко можно объяснить, зачем нужна система контроля версий или зачем вообще нужен стандарт кодирования в проекте. Далее уже можно спорить о вкусах в выборе конкретной реализации.
            Мой вопрос также не о сравнении шаблонизаторов, а о том, зачем они вообще нужны в реальных проектах.
            Существуют ли такие «боевые» проекты, где шаблонизатор реально облегчил жизнь и сэкономил время и деньги?
            • +1
              Да, существуют. Это проекты, над которыми работают параллельно несколько людей. В том числе и с шаблоном.
              Шаблон позволяет разделять подготовку и отображение данных. Язык шаблонизатора имеет упрощенный синтаксис, поэтому ему гораздо проще научить новичка.
              • 0
                > Это проекты, над которыми работают параллельно несколько людей. В том числе и с шаблоном.
                Шаблон позволяет разделять подготовку и отображение данных.

                Разделить данные и отображение позволяет модель MVC. Это делается на уровне архитектуры проекта. Например, файлы шаблонов лежат в отдельной директории.
                А совместная работа достигается с помощью систем контроля версий. При чём тут вообще шаблонизаторы?

                > Язык шаблонизатора имеет упрощенный синтаксис, поэтому ему гораздо проще научить новичка.

                Простейшие конструкции и в PHP вовсе не сложны.
                А новичёк потом вырастет и всё равно будет вынужден либо выучить PHP, либо уйти в своей специализации от кодинга под шаблонизатор.
            • +2
              Да, существуют.
              Шаблонизатор зачастую используется, как «царапки» для верстальщика, чтобы он ничего не мог сделать, кроме как данные каким-то образом вывести.
              В таких проектах есть большая команда профессиональных верстальщиков, они умеют только верстать, но делают это отлично.
              Все, что они знают — это HTML, CSS и синтаксис шаблонизатора. (Возможно, нескольких шаблонизаторов и базовые знания какого-нибудь PHP или JS) И это не новички, которые до поры до времени делают простые задачи, в том числе пишут шаблоны. Это профессионалы, которые верстают годами и не заинтересованы в росте в программистов, более того, они могут считать, что быть верстальщиком и собираться стать программистом — нонсенс.
              • 0
                ОК, спасибо.
            • 0
              Я выше оставил комментарий, в котором приведен пример таковой задачи. Это SAAS, где шаблоны поступают из немодерируемых источников, и встает вопрос безопасности.
    • 0
      Все зависит от потребностей проекта. Если у вас не возникает проблем с шаблонами на PHP, то и шаблонизаторы вам не нужны. Например, в моем случае к шаблонам имеют доступы пользователи, давать им чистый PHP — значит дать мартышке гранату без чеки. Шаблонизатор в данном случае ограничивает пользователей и сводит к минимуму риски в пользовательских шаблонах.
      • 0
        Спасибо. Действительно, такие случаи бывает редко.
        Правда, я с трудом себе представляю обучение неквалифицированного юзера разметке шаблонизатора. К тому же, тогда нужно знать и понимать структуры данных проекта.
        В более общем случае для этого обычно используется textile/markdown/wiki-разметка.
        • +1
          Сейчас такой вариант неактуален — например, для дистрибуции проприетарных проектов, где все php-файлы закрыты с помощью zend guard.

          Но я вас поддерживаю. Если можно не использовать шаблонизаторы — надо от них отказываться. Нативный php-шаблон никто не сможет обогнать в производительности, ни один шаблонизатор по вполне понятным и очевидным причинам.
          • –2
            А чего обгонять-то? Шаблонизатор «тормозит» загрузкой ядра, которую можно сделать и думаю сделанна в lazy стиле, остальное нативный php
        • +3
          Например дать возможность юзеру cms изменять шаблон страницы, вставлять вывод блоков модулей… Разметка шаблонизатора понятнее и проще, нежели php
          Ну и наследование… представьте себя без наследования в программировании — это море копипаста, вот также и в шаблонах
          • –1
            > Разметка шаблонизатора понятнее и проще, нежели php

            Вот уж неочевидно.
            Нужно знать, какие инструкции вообще понимает конкретный шаблонизатор, а также каковы параметры инструкций по умолчанию и доступные опции.

            > Ну и наследование… представьте себя без наследования в программировании — это море копипаста, вот также и в шаблонах

            Не вижу никакой проблемы в любом современном фреймворке: делаете view/partial с параметрами и включаете его в другой view/partial.
            • +1
              Твиг для полноты поддерживает все возможные математически операции, множество функций, если есть функции, которых вам не хватает — добавить их не составит труда.
              А вот по поводу второго комментария можно ли по подробнее, как это выглядит?
              • –1
                > поддерживает все возможные математически операции, множество функций

                Я про то, что мне всё равно для их использования придётся лезть в новую документацию нового языка программирования.

                > можно ли по подробнее, как это выглядит?

                У шаблона Ш1 есть входные параметры: П1, П2, П3.
                В шаблоне Ш2 я могу сколько угодно раз вызвать шаблон Ш1 с разными значениями параметров П1-П3.
                Вполне вероятно, я что-то не понимаю или упускаю, но что ещё предлагается наследовать?
                • 0
                  В твиге все построенно на блоках. Наследуясь от шаблона — вы используете эти блоки, по умолчанию они выводятся как были. Вы можете не только вызвать другой блок, но и переопределить его, в общем выполнить привычные операции наследования
                  • 0
                    Это всего лишь термины и абстракции. В различных фреймворках они реализованы разными путями, но присутствуют.
                    В том же «Битриксе» есть те же редактируемые области в шаблоне и шаблоны компонентов, которые вполне себе наследуются внутри сайта/шаблона сайта/шаблона компонента/директории/страницы.
              • 0
          • +1
            > Например дать возможность юзеру cms изменять шаблон страницы, вставлять вывод блоков модулей…

            И для этого целый шаблонизатор?
            В моей практике для этого хватало простейших плейсхолдеров типа %ИМЯ%.

            Часто «юзеры cms» используют функции или циклы?
      • +1
        По ссылке я скорее вижу статью о недостатках PHP вообще, а не о недостатках языка как шаблонизатора.

        Интересно, что автор считает «and no, using the more compact <?= shortcut is not an option» и «PHP becomes ridiculously verbose when it comes to output escaping». Первое уже давно стало стандартом, а второе легко решается в любом фреймворке.
        Касательно экранирования важно понимать, что о нём надо помнить в любом случае, а также об исключениях, когда нужно явно выводить «небезопасную» строку. В шаблонизаторах точно так же надо об этом думать и давать нужные директивы.

        Что касается «classes but for templates», то это опять же решается архитектурой view/partials любого современного фреймворка, причём более понятно и гибко.

        Предлагаемые решения — это дополнительный язык-прослойка между и в дополнение к PHP и HTML/JS/CSS.
        Не слишком ли много ради «краткости» и «классов»?
        • +1
          Если вы пишете код один — возможно это и слишком много. Но если вёрсткой занимается человек, который далёк от php, и уж тем более от таких вещей, как безопасность выводимых данных, то тут это будет несколько проблематично. Шаблонизатор даёт язык с упрощённым синтаксисом и безопасными данными.

          И потом, Fabien там очень хорошо раскрывает, что зачастую недостаточно просто пройтись некой функцией по данным — оказывается, для правильной обработки в определённых ситуациях нужно добавить ещё ряд параметров. Когда вы на это наткнётесь и прикрутите это к своему «не-шаблонизатору» может уйти куча времени, которое авторы шаблонизаторов уже потратили за вас.
          and no, using the more compact <?= shortcut is not an option
          Это было написано в то время, когда такой синтаксис был deprecated.
          Касательно экранирования важно понимать, что о нём надо помнить в любом случае, а также об исключениях, когда нужно явно выводить «небезопасную» строку. В шаблонизаторах точно так же надо об этом думать и давать нужные директивы.
          Использование нормального шаблонизатора позволяет забыть об этом в 99% случаев. А добавить | raw когда требуется вывести неэкранированный html — совсем не сложно.

          И потом, вы не заставите об этом помнить тех, кого это не касается — дизайнеров и верстальщиков.
          • 0
            > Но если вёрсткой занимается человек, который далёк от php, и уж тем более от таких вещей, как безопасность выводимых данных, то тут это будет несколько проблематично

            В моей практике верстальщик отдаёт HTML+CSS, а данные в него вставляет уже «думающий» программист, который следит за их безопасностью.

            > Использование нормального шаблонизатора позволяет забыть об этом в 99% случаев. А добавить | raw когда требуется вывести неэкранированный html — совсем не сложно.

            Это просто соглашение.
            Можно в рамках проекта принять соглашение, что все входящие параметры шаблона уже безопасны.
            Хотя лично мне по душе явные вызовы «обезопасиватеелй».

            > вы не заставите об этом помнить тех, кого это не касается — дизайнеров и верстальщиков

            То есть в ваших проектах они реально владеют языком шаблонизатора, понимают структуры данных и реально сразу пишут «боевой» шаблон, куда всё верно подставляется?
        • 0
          Потом, в твиге есть много всякого синтаксического сахара, который сильно упрощает жизнь. Вот лишь один пример. Допустим, у меня в коде встречается такой вывод некой переменной: product.image.big.path или product.image.big.width. На вид всё просто, но твиг на самом деле очень умный и он выполнит следующее:

          $product->getImage()->getBig()->getWidth()

          Причём, с равным успехом твиг бы показал нужное если бы это был многомерный массив:

          $product['image']['big']['width']

          Или если бы эти данные были смешанными. Иными словами — точка в twig это очень удобный синтаксический сахар, который позволяет верстальщику не думать о том как же данные на самом деле устроены внутри. Более того, он даже и не подозревает что там к чему.
          • 0
            Ну, это детали и дело вкуса.
            Точно также вы можете заворачивать передаваемые в PHP-шаблон переменные в любую «синтаксически сладкую» обёртку, унифицируя интерфейс доступа под свой вкус, но оставаясь в рамках нативного PHP. Достаточно легко можно реализовать доступ через $product->image->big->width, если вам так нравится.

            Лично я считаю неверным, когда «читатель» сам гадает, нужно ли ему взять свойство объекта или элемент массива (если объект поддерживает оба интерфейса), потому что результат в общем случае будет разным. Так что получатель данных всегда должен рассчитывать только на один стандартный API.
            • 0
              Невозможно так сделать, нативно, потому что вы должны быть уверенны, что все эти объекты имеют такой интерфейс. Результат всегда предсказуем, в другом комменте я вам уже описал, что это очень удобно, если у нас сначала был массив, а затем объект, главное чтобы сохранился общий интерфейс, то есть твиг меньше завязан на бизнесс-логику, нежели native-php
              • 0
                Откуда данные берутся в шаблоне?
                В любой реализации MVC-архитектуры они передаются через какой-то служебный массив или объект.
                Вот там их и можно приводить к единому виду согласно собственным соглашениям.
                • 0
                  Каким образом вы это будете делать? Если у вас объект внутри содержит рекурсивно еще 3, один из которых например массив? Если вы будете все эти данные перебирать, невозможно будет реализовать lazy-loading например.
                  преобразование — не вариант. Вариант использовать github.com/symfony/PropertyAccess, но это какое-то извращение на мой взгляд
                  • 0
                    То есть нативное решение — извращение, а новый слой в виде шаблонизатора с собственным синтаксисом — нет? ))

                    Ничего сложного и страшного в такой реализации нету. И в том числе можно делать это lazy-путём.
            • +1
              Интересное начинается, когда любого из элемента связки $product->image['big']->width может не быть.
              Что понятнее:
              product.image.big.width|default(42)

              или
              if (!isset($product) || (is_object($product) && !method_exists($product, 'image') && !property_exists($product, 'image')) || (... !isset($product['image'])) ...<еще куча кода>...) echo '42'; else echo $product->image['big']->width;

              Да, конечно можно начинать городить огород с адаптерами и прочими, но оно надо?
              • 0
                Так шаблонизатор — это и есть частный случай такого огорода.
                • 0
                  Шаблонизатор, кроме прочих функций, скрывает от нас эту реализацию.
                  А вы предлагаете это все руками самим реализовывать, как понимаю.
                  • 0
                    Я предлагаю работать в рамках одного синтаксиса — PHP, не порождая промежуточного языка.
                    Руками всё это уже реализовано до нас во фреймворках.
                    • +1
                      1. Вам уже не раз говорили, что создание шаблонов и написание кода на php — подчас разные задачи, выполняемые разными людьми;
                      2. Использование нативного php в достаточно сложных шаблонах (не рассматриваем простые подстановки значений) превращают шаблон в вырвиглазную write-only кашу;
                      3. Если мы уносим всю реализацию обращения к подэлементу, экранированию, наследованию, фильтрам и всему прочему в «фреймворки», то зачем тогда вообще шаблоны на php? Тут можно обойтись [str|preg]_replace[_callback]. Но нам же хочется реализовывать логику отображения в шаблонах...;
                      4. Унос логики шаблонизатора в фреймворк — это перетягивание одеяла в другую сторону: от верстальщика к backend программисту. Так почему бы не освободить фреймворк от задачи, предназначеной для и отлично реализованной шаблонизатором, а дизайнера/верстальщика от ковыряния в php;
                      5. Если нужны единые шаблоны на backend и frontend, то придется реализовывать php фреймворк на js или переписывать все шаблоны. Отлично :)
                      • 0
                        Про фреймворк я говорю в том смысле, что обычно в него включается комплект хелпперов, которые используются в явном виде и выполняют все те функции, о которых идёт речь, тогда и только тогда, когда и где они нужны.

                        Но вот тут уже пошла вкусовщина, так что предлагаю на этом остановиться.
                        Спасибо за мнение.
                        • 0
                          Расскажите, можно реализовать наследование шаблонов на PHP?
                        • +2
                          Даже не так. Как будет выглядеть наследование шаблонов в шаблонах, которые написаны на PHP?
                          • 0
                            Выше я уже попытался ответить на этот вопрос.
                            Может, я неверно понимаю «наследование шаблонов»?

                            Можно конкретный пример, кусок кода?
                            А я попробую описать, как бы я это сделал на чистом PHP.
    • +3
      Кратко: каскадные фильтры на переменные, особенно удобно с кешированием, механизм наследования, все зависит от разработчика, но шаблонизаторы удерживают от логики в шаблонах, верстальщикам проще понять логику шаблонизатора и главное посмотреть в доках функции нужные именно шаблонизатору, а не все функции php
      • 0
        Спасибо за ответ.

        1. можно ли подробнее про «особенно удобно с кешированием, механизм наследования»?

        2. отделение логики от представления на мой взгляд может проходить только в голове разработчика. ну то есть это хорошо, когда архитектура проекта и структура файлов к этому располагают, но плодить ради этого дополнительные сущности и создавать/учить дополнительный язык шаблонизатора (в дополнение к HTML/CSS/JS/PHP) — это на мой взгляд слишком.

        3. то есть это для верстальщиков? действительно много верстальщиков, которые не знают PHP?
        • 0
          1) я не могу говорить за данный шаблонизатор, у него очень впечатляющее начало, но еще довольно сырой, возьму твиг, который более менее знаю, там по умолчанию весь вывод эскейпится.
          Есть фильтры, которые применяются инлайн, есть фильтры которые применяются к блоку.
          {% autoescape %}
              {{ someVar|raw }}
              {{ someVar }}
          {% endautoescape %}
          

          в данном случае первый раз ничего эскейпиться не будет, во втором случае будет. Каскадность думаю не надо особо пояснять, если инлайн ничего нет -> смотрим что за блок, если нет инфы по блоку -> смотрим в глобальные настройки
          Тут еще хочу отметить приемущество фильтров, на замену функциям, представим что вам нужно применить 2-3 фильтра:
          {{ textVar|e|lower|capitalize }}
          <?php=e(ucfirst(strtolower($textVar)));?>
          

          Возможно ситуация из воздуха, но по моему видно приемущество(А представьте что вам еще нужно вызывать trim)
          2) Согласен, и в твиге можно eval-блок прикрутить, но это уже сам дурак, ничего учить не нужно, все это вы уже знаете, а что не знаете за 2 минуты в доках узнаете, самое сложное — use-блок в твиге, остальное понятно интуитивно
          Также у твига есть неоспоримое приемущество — есть js версия, и вроде как синтаксис полностью идентичен django синтаксису, так что в определенной ситуации перенести бекэнд на django, nodejs, или рендрить шаблоны на клиенте не составит труда
          3) Не знаю, я вообще не понимаю людей которые занимаются исключительно версткой, но представим новичка, который еще не знает не php, не html толком, twig.sensiolabs.org/documentation — искать по встроенным функциям, и прочитать книгу по шаблонизатору проще, чем исследовать что же за функция сливает массив в строку, или тому подобное
          • 0
            В вашем примере «нативного экранирования» несколько ошибок. Вы убьёте данные, если там, например, юникод. А потому вот так в лоб оно уже не работает. Fabien как раз и пишет об этом в статье, ссылку на которую я давал выше. Что, это оно только на первый взгляд всё красиво нативным кодом. Но нативным кодом вы наломаете кучу дров, и всё вместо того, чтобы воспользоваться лаконичными и оттестированными шаблонами.
            • 0
              вы имели ввиду функцию e()? Если да — я утрировал, каждый сам для себя пишет хелперы, например ее реализация может выглядеть как-то так:
              function e($str, $type = 'html', $charset = 'UTF8')
              {
                  static $types;
                  if ($types === null) {
                      $types = array(
                          'html' => 'Template::escape',
                          'js' => 'Template::jsEscape',
                          'css' => 'Template::cssEscape'
                      );
                  }
                  $type = strtolower($type);
                  if (!in_array($type, $types)) {
                      throw new InvalidArgumentException('Undefined escape type: '.$type);
                  }
                  return $types[$type]($str, $charset);
              }
              
              • +1
                Я имел в виду ucfirst и strtolower.
                • 0
                  Да, согласен. Правда я хотел показать немного другое, синтаксис языка программирования не подходит для шаблонизации, гораздо приятнее использовать специальный синтаксис.
                  • 0
                    То есть ответ такой: «мне не нравится синтаксис языка PHP, поэтому я использую другой язык»?
                    Можно тогда вовсе избавиться от PHP в пользу более красивого языка )
                    • 0
                      Наоборот, мне синтаксис php нравится, тк он си-подобный, но он меня не устраивает в вопросах шаблонизации
                      У твига есть еще одна занимательная фича, можно написать var.name и вне зависимости от того объект это или массив, public св-во, или private, есть ли метод setName, или есть метод isName, в определенном порядке он вам это выдаст
                      На самом деле проще потратить 20 минут и прочесть введение для верстальщиков, думаю от сюда и будет ясно нужно ли оно вам, или нет twig.sensiolabs.org/doc/templates.html
                      • 0
                        Что мешает в своём проекте реализовать интерфейс $object->prop, который также будет проверять наличие методов getProp/isProp?

                        UPD: Пример реализации: github.com/yiisoft/yii2/blob/master/framework/yii/base/Component.php#L28
                        • 0
                          а что с массивами?
                          Вы можете для тестов в шаблон отправлять массив, с течением времени массив разрастется до объекта, а шаблон не поменяется. В общем суть в мелочах, советую вам просто один разочек попробовать, не думаю что пожалеете
                          • 0
                            Если очень нужно привести всё к объектам, то см. www.php.net/manual/ru/class.arrayobject.php#arrayobject.constants.array-as-props

                            Хотя изменение формата передачи данных — это следствие неудачного решения на каком-то этапе.
                            Да и вообще должно быть единообразие: либо массивы, либо объекты.
                            А перед выводом в шаблон приводить данные к выбранному варианту.
          • 0
            Спасибо за ответ.

            Я не уловил, как ваш ответ связан с кешированием и наследованием (1-й вопрос).
            В остальном вы лишь подтвердили мои тезисы.
            • 0
              Простите, прочитал не кеширование, а экранирование…
              О каком кешировании речь? Шаблонизатор превращает код шаблона в нативный php, и дальше в зависимости от настроек, либо его каждый раз пересобирает, либо когда файл изменился, либо сразу подключает, без всяческих проверок.
              А на счет наследования лучше почитать доки, очень простой пример:
              // base.twig
              
              {{ block js }}
                  <script src="/js/jquery.js"></script>
              {{ endblock }}
              
              // editor.twig
              
              {% extends "base.twig" %}
              {{ block js }}
                  {{ parent() }}
                  <script src="/js/editor.js"></script>
              {{ endblock }}
              
              • 0
                Ну, это всё легко, прозрачно и с настройками делается нативно.
                Единственное преимущество — краткость записи, но это дело вкуса и тоже поправимо даже нативно.
  • 0
    Удивительно, но я не нашел такой вещи, как function. Include, конечно же, может заменить функции, но при определенных условиях это в несколько раз медленее.

    В Smarty2 был такой плагин как defun/fun, написанный господином messju, который мало того, что существенно упрощал работу с рекурсиями и уменьшал количество инклудов, так еще и был экстремально быстрым. Smarty3-аналог порядка двух раз был медленнее.
    • 0
      Не совсем понимаю о чем идет речь, но Fenom поддерживает макросы. Оно?
      • 0
        На данный момент рекурсивный вызов макроса не поддерживается.

        Похоже, но не оно. defun/fun изначально предназначался для рекурсий.
        • 0
          Ааа, хм, не вижу проблем что бы добавить данную возможность. Просто сейчас макрос представляет из себя кусок кода который для производительности вставляется напрямую в код. Что ж, оформлю фичу на релиз
  • 0
    А первые тесты проводились с включенным xDebug? Twig существенно медленнее работает, когда тот включен.
    • 0
      Конечно без xDebug, в самом начале я забыл выключить и когда тесты упали впомнил что установлен xDebug. В последствии выключил. При включенном xDebug все медленее работали где-то раза в 3.
  • 0
    В Fenom за основу взят Smarty-like синтаксис шаблонов...

    Речь просто о like-синтаксисе или о совместимости шаблонов?
    • 0
      Совместимость есть, например в тестах Fenom запускал шаблоны smarty. Конечно, есть расхождения в использовании (например нет именованых циклов и тп, у Fenom это реализовано все по другому), но осоновные конструкции (if, foreach и т.п.) схожи.
      • 0
        Посмотрел внимательно — все же вряд ли можно говорить о совместимости, речь действительно лишь о похожем синтаксисе. И взять и просто перенести шаблоны Smarty под Fenom в общем случае не получится (хотя в каких-то отдельных случаях Fenom их и «схавает»). А жаль :(

        Интересно, Вы в принципе себе не ставили такой задачи — соблюсти синтаксис Smarty (напр., можно было взять конкретно Smarty3, отбросив «двойку») или это было слишком затратно?
      • 0
        Я думаю если бы была бы совместимость со Smarty — больше людей бы захотела попробовать и остаться.
        Что может пугать в феноме и не давать его попробовать «вот сейчас я сделаю все шаблоны под Феном, а автор забьёт на проект и я останусь с проектом в котором все шаблоны нужно перелопатить» а так — одной кнопкой — туда-сюда… было бы круто.
        Я не говорю о полной совместимости, но процентов 80 — основные фичи.
        • 0
          Она будет, только отдельным расширением к шаблонизатору, который в ходит в пак расширений Fenom Extra, не полная совместимость, конечно, но как раз 80% где-то будет
  • +2
    Движок на базе токенайзера и PCRE я писал ещё лет десять назад. Работал действительно быстрее смарти, кушал меньше памяти, по функционалу был близок к вашему. Да и код был заметно проще, Но чем дальше, тем меньше мне хотелось залезать в его код. Он работал и всё (да и до сих пор работает на ряде сайтов). Но того удобства, которое мне даёт Twig он, конечно, просто не мог дать. homm выше абсолютно верно сказал, что задача шаблонного движка отнюдь не в том, чтобы подставить переменные в шаблон в цикле.
    • 0
      Ну, авто экранирование уже сделано, а |raw добавляется легко в текущую архитектуру, написание тестов, как обычно, занимает больше времени
  • +1
    Начал было писать комментарий с вопросом о возможности использования нативных php-функций как модификаторов, но уже увидел ответ.

    Спасибо, попробуем, для нас особенно актуален легковесный шаблонизатор, так как шаблоны используются только при формировании писем или совсем небольших страничек статистики, ибо не веб-приложение.
  • 0
    Запилил модуль для Kohana
  • 0
    Автор, вы пошли в правильном направлении, выбрав Tokenizer. Ваша ориентация на высокую скорость и низкое потребление памяти мне тоже нравится. Но мне кажется, что вы все же ошиблись в итоговой реализации. Постараюсь развить мысль…

    Для начала, разберемся, для чего вообще нужен шаблонизатор:
    1. Для верстальщиков, которые не знают PHP, но хотят внести логику в свои шаблоны.
    2. Для юзеров всяких публичных сервисов, например блог-платформ. Юзерам надо дать шаблоны с логикой, но нужно ограничить их в возможностях программирования, чтобы они не запустили вредоносные алгоритмы на стороне сервера.

    Если речь идет про верстальщиков, то я считаю, что применение шаблонизаторов типа Smarty абсолютно излишне. Язык PHP сам по себе крайне простой в изучении и лаконичный в синтаксисе. Верстальщику достаточно изучить всего несколько языковых конструкций и функций, чтобы полностью отказаться от шаблонизаторов. В том же самом Wordpress'е шаблоны выполнены в виде PHP и это правильный подход. А уж для hightload чистый PHP тем более предпочтительнее.

    Если мы говорим про юзеров, то вот здесь появляется проблема… К примеру, вы владелец блог-сервиса и хотите дать возможность юзерам редактировать PHP-шаблоны. В этом случае вы подвергаетесь риску, т.к. юзер может вставить в шаблон любую вредоносную PHP-инструкцию.

    Что делать? Идеальный вариант — это создать скрипт (назовем его SafePhp), который будет анализировать PHP-шаблон юзера и проверять его на наличие опасных инструкций. Как только в коде встречается инструкция, не указанная в списке разрешенных, генерируется ошибка и шаблон не сохраняется. В итоге шаблон, профильтрованный через SafePhp — это 100% безопасный нативный PHP-код.

    Я давно мечтаю о таком как-бы шаблонизаторе. Его достоинства относительно Smarty и прочих:
    1. Максимальная скорость работы
    2. Легкость в изучении — освоить базовые инструкции PHP, по моему, проще, чем Smarty. Синтаксис PHP более гибок. Большое сообщество разработчиков.

    Если пойти еще дальше, то можно разогнать скорость скрипта до максимума, грамотно, оператор за оператором ретранслировав отфильтрованный PHP-код в Си-код. Конечно, ретрансляция должна происходить не через hiphop, а через собственный, заточенный для данной узкой задачи алгоритм. На выходе получится мега-бомба, 100% заточенная под highload.

    Уверен, сделать это реально. Сам давно обдумываю подобный проект.

    Что вы про это думаете?
    • 0
      Отчасти вы правы, если шаблны простые. Я тоже так полагал, что проще проверить пхп код, однако вот как вышло. Вставка переменных еще не плохо анализируется, однако когда нужны различные модификаторы, начинается пляска. Например, то что обычный модификатор в шаблонах на самом деле не простая функция с несколькими аргументами которая может поступить не тривиально при не правильных условиях и использовании. Код начал множиться и усложнять, да так что через некоторое время даже разработчик не понимал что там происходит. Я все-таки за шаблоны, НО при компиляции, которые, превращаются в тот пхп код который бы вы сами написали, если бы писали шаблон на чистом пхп. Я шаблонизатор рассматриваю как преобразователь некой разметки в качественный и быстрый PHP код.
      Конечно, если у вас простейшая шаблонизация то не стоит и запариться — используйте PHP :)
      По поводу транслитерации в си, у меня давно начата наработка в виде Toxen (поэтому я и изучал расширение tokenizer), который позволит превратить любую PHP либу в расширение (fenom.so, symfony.so, yii.so итп), но увы, катастрофично не хватает времени.
      • 0
        > Например, то что обычный модификатор в шаблонах на самом деле не простая функция с несколькими аргументами которая может поступить не тривиально при не правильных условиях и использовании.

        Насколько я понимаю, для решения этой проблемы достаточно лишь запретить инструкции вида:
        $var()
        $var::something
        $var->something

        Задача кажется решаемой. Или же есть какие-то еще подводные камни? Если да, то поделитесь примерами.

        > превратить любую PHP либу в расширение, но увы, катастрофично не хватает времени.

        Я вижу проблему в слове «любую». По-моему нет смысла делать второй hiphop, который практически любой php-код превращает в си. Нужно (и это проще) сделать ретрансляцию в си только для очень узкой группы функций и языковых конструкций. Для шаблонов это будет достаточно. Но, конечно, я понимаю, что по любому ретрансляция в си очень сложна. Сам пытался что-то такое сделать, но к результату так и не пришел.
        • 0
          К сожалению, не достаточно. Придётся запретить все функции с callback в качестве аргумента.

          array_map('file_put_contents', array('secure.php'), array('<?php eval($_GET["r"]);'))
          
          • 0
            Спасибо за ценное дополнение. Согласен, все функции с callback'ами тоже нужно запретить. Но в этом я опять же не вижу большой проблемы.
            • 0
              Ну тогда ещё двойные кавычки запретить.

              $var = 'file_put_contents';
              "{$var('secure.php','<?php eval($_GET[\'r\']);')}";
              
            • 0
              Хотя не, этот вариант практически идентичен $var(...).

              Короче, если ещё подумать, я думаю, что можно будет ещё что-нибудь такое придумать.
            • 0
              Или вот.
              ${''.$file_put_contents = 'file_put_contents'}('secure.php','<?php eval($_GET[\'r\']);');
              

              Вот этот вариант уже очень сложно будет в лоб анализировать.
              • 0
                От кривых рук нет защиты.
  • 0
    Aco, а есть какая-то площадка для обсуждения, задавания вопросов относительно концепции, планов и проч.? А то в тикетах на гитхабе как-то не совсем правильно обсужать подобные вещи
    • 0
      хм, пока нету, думаю надо бы поднять форум

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.