Как стать автором
Обновить

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

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

Интересно! Также думаю если кто-то из русского сообщества захочет разобраться со внутренностями, отправить патч и тд, будет полезно почитать об этом
Хочу бенчмарка Fenom vs native PHP с реализацией типа ob_start.
Я тоже планировал это сделать, но не смог нащупать ту самую границу когда шаблон становится не нативным. Например для наследования нативно не получится написать шаблон, нужны классы, методы и тд… микро шаблонизатор, что-ли.
В итоге получалась какая-то копипаста из шаблонов 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 (
  ),
));


Я, прежде всего, про то, что при применении нативного 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);
    }
}
Так нативные шаблоны ничем не отличаюся от скомпилированных, если правильно компилировать. Возможно даже некоторые моменты шаблонизатор лучше заоптимизирует, но по хорошему явно не хуже. А проиграет только на этапе компиляции
Как раз интересует «лучше заоптимизирует» сравнить.
НЛО прилетело и опубликовало эту надпись здесь
Голова разработчика может случиться, которая не даст просто взять и лапшойнаписать PHP код, ведь нужны классы, интерфейсы, абстакция и тд. Ни кто уже не пишет спагетти-код кроме шаблонизаторов. А такую вещь как наследование шаблонов на чистом спагетти PHP не получится реализовать.
НЛО прилетело и опубликовало эту надпись здесь
Сегодня обнаружил одну неприятную особенность Twig — какая-то жуткая потеря производительности при использовании вложенных массивов и проверки нахождения ключей в них в цикле… начал смотреть как ту же ситуацию обработают другие шаблонизаторы, вот и про Fenom вспомнил. Получилось как-то так

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

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

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

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

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

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

   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);';
    }

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

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

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 будет все равно применяться и портить строку.

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

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

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

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

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

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

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

А для управления блоками raw в тех же Jinja/Django templates/Twig сделан не только фильтром, но и блочным тегом (правда называется verbatim).
Вас тоже этот вид 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 — игнорирование тегов шаблонизатора.
Да, с 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 %}
НЛО прилетело и опубликовало эту надпись здесь
Не стоит удивляться. Я, конечно, был в курсе об автоэкранировании, но работая в highload проектах, данные подготавливаешь заранее потому что производительнее заэкранировать один раз данные перед сохранением в базу, чем экранировать каждый просмотр. Если сделать холодный расчет: 0.1ms занимает экранирование, допустим элементов отображается 50 на страницу, есть заголовок, описание и еще 3 текстовых поля у каждого элемента. Это 0.1 * 50 * 5 = 25ms, итого +25ms ко времени загрузки страницы, что довольно существенно для highload. Тем не менее я добавил экранирование, но по умолчанию эта опция выключена.
НЛО прилетело и опубликовало эту надпись здесь
Шаблонизатор только-только и печи и сейчас не пертендует везде на полную замену популярных шаблонизаторов. Все приходит со временем. Со временем я добавлю более боевые тесты и больше функциональности. А пока я приглашаю поучавствовать в развити проекта.
НЛО прилетело и опубликовало эту надпись здесь
Шаблонизатор стабилен, покрыт тестами. В коментрарии я предлагаю присоеденится и наростить необходимую и недостающую фугкциональность.
> Op-кешеры, конечно же, отключены

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Часто «юзеры cms» используют функции или циклы?
По ссылке я скорее вижу статью о недостатках 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.
Не слишком ли много ради «краткости» и «классов»?
Если вы пишете код один — возможно это и слишком много. Но если вёрсткой занимается человек, который далёк от php, и уж тем более от таких вещей, как безопасность выводимых данных, то тут это будет несколько проблематично. Шаблонизатор даёт язык с упрощённым синтаксисом и безопасными данными.

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

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

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

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

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

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

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

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

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

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

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

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

Ничего сложного и страшного в такой реализации нету. И в том числе можно делать это lazy-путём.
Интересное начинается, когда любого из элемента связки $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;

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

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

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

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

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

3. то есть это для верстальщиков? действительно много верстальщиков, которые не знают PHP?
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 — искать по встроенным функциям, и прочитать книгу по шаблонизатору проще, чем исследовать что же за функция сливает массив в строку, или тому подобное
В вашем примере «нативного экранирования» несколько ошибок. Вы убьёте данные, если там, например, юникод. А потому вот так в лоб оно уже не работает. Fabien как раз и пишет об этом в статье, ссылку на которую я давал выше. Что, это оно только на первый взгляд всё красиво нативным кодом. Но нативным кодом вы наломаете кучу дров, и всё вместо того, чтобы воспользоваться лаконичными и оттестированными шаблонами.
вы имели ввиду функцию 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);
}
Я имел в виду ucfirst и strtolower.
Да, согласен. Правда я хотел показать немного другое, синтаксис языка программирования не подходит для шаблонизации, гораздо приятнее использовать специальный синтаксис.
То есть ответ такой: «мне не нравится синтаксис языка PHP, поэтому я использую другой язык»?
Можно тогда вовсе избавиться от PHP в пользу более красивого языка )
Наоборот, мне синтаксис php нравится, тк он си-подобный, но он меня не устраивает в вопросах шаблонизации
У твига есть еще одна занимательная фича, можно написать var.name и вне зависимости от того объект это или массив, public св-во, или private, есть ли метод setName, или есть метод isName, в определенном порядке он вам это выдаст
На самом деле проще потратить 20 минут и прочесть введение для верстальщиков, думаю от сюда и будет ясно нужно ли оно вам, или нет twig.sensiolabs.org/doc/templates.html
Что мешает в своём проекте реализовать интерфейс $object->prop, который также будет проверять наличие методов getProp/isProp?

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

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

Я не уловил, как ваш ответ связан с кешированием и наследованием (1-й вопрос).
В остальном вы лишь подтвердили мои тезисы.
Простите, прочитал не кеширование, а экранирование…
О каком кешировании речь? Шаблонизатор превращает код шаблона в нативный 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 }}
Ну, это всё легко, прозрачно и с настройками делается нативно.
Единственное преимущество — краткость записи, но это дело вкуса и тоже поправимо даже нативно.
Удивительно, но я не нашел такой вещи, как function. Include, конечно же, может заменить функции, но при определенных условиях это в несколько раз медленее.

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

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

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

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

Спасибо, попробуем, для нас особенно актуален легковесный шаблонизатор, так как шаблоны используются только при формировании писем или совсем небольших страничек статистики, ибо не веб-приложение.
Автор, вы пошли в правильном направлении, выбрав 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.

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

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

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

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

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

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

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

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

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

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

Публикации

Истории