Dirt-простые шаблоны PHP … может ли это работать без `eval`?

Обновление – спасибо за все ответы. Этот Q становится грязным, поэтому я начал сиквел, если кому-то это интересно.


Я собрал быстрый сценарий для друга и наткнулся на действительно простой способ делать шаблоны в PHP.

В принципе, идея состоит в том, чтобы проанализировать html-документ как строку heredoc, поэтому переменные внутри него будут расширены PHP.

Функция пересылки позволяет оценивать выражения и функции и вызовы статических методов внутри строки:

function passthrough($s){return $s;} $_="passthrough"; 

Код для синтаксического анализа документа внутри строки heredoc нелепо прост:

 $t=file_get_contents('my_template.html'); eval("\$r=<<<_END_OF_FILE_\n$t\_END_OF_FILE_;\n"); echo $r; 

Единственная проблема заключается в том, что она использует eval .

Вопросов

  • Может ли кто-нибудь подумать о способе делать подобные шаблоны без использования eval , но не добавляя парсер или тонну безумия регулярного выражения?

  • Любые предложения по экранированию запрещенных знаков доллара, которые не относятся к переменным PHP без написания полного парсера? Означает ли проблема бродячих значков доллара этот подход нежизнеспособным для «серьезного» использования?


Вот пример шаблона HTML-кода.

 <script>var _lang = {$_(json_encode($lang))};</script> <script src='/blah.js'></script> <link href='/blah.css' type='text/css' rel='stylesheet'> <form class="inquiry" method="post" action="process.php" onsubmit="return validate(this)"> <div class="filter"> <h2> {$lang['T_FILTER_TITLE']} </h2> <a href='#{$lang['T_FILTER_ALL']}' onclick='applyFilter();'> {$lang['T_FILTER_ALL']} </a> {$filter_html} </div> <table class="inventory" id="inventory_table"> {$table_rows} <tr class="static"><th colspan="{$_($cols+1)}"> {$lang['T_FORM_HELP']} </th></tr> {$form_fields} <tr class="static"> <td id="validation" class="send" colspan="{$cols}">&nbsp;</td> <td colspan="1" class="send"><input type="submit" value="{$lang['T_SEND']}" /></td> </tr> </table> </form> 

Зачем использовать шаблоны?


Обсуждалось, нужно ли создавать шаблонный слой в PHP, что, по общему признанию, уже довольно хорошо подходит для шаблонов.

Некоторые быстрые шаблоны шаблонов полезны:

  • Вы можете контролировать это

    Если вы предварительно обработаете файл до того, как он перейдет к интерпретатору, вы получите больше контроля над ним. Вы можете вводить материал, блокировать разрешения, очищать вредоносный php / javascript, кэшировать его, запускать через шаблон xsl, что угодно.

  • Хорошая конструкция MVC

    Templating способствует отделению зрения от модели и контроллера.

    При переходе в теги <?php ?> В вашем представлении легко лениться и выполнять некоторые запросы к базе данных или выполнять другие действия с сервером. Используя метод, подобный вышеизложенному, только один оператор может использоваться на «блок» (без запятой), поэтому гораздо труднее попасть в эту ловушку. <?= ... ?> имеют почти такую ​​же выгоду, но …

  • Короткие метки не всегда включены

    … и мы хотим, чтобы наше приложение запускалось в различных конфигурациях.

Когда я сначала взламываю концепцию вместе, она начинается как один файл php. Но прежде чем он вырастет, я не доволен, если все php-файлы не имеют только один <?php в начале, а один ?> В конце, и предпочтительно все классы, кроме таких, как контроллер, настройки, сервер изображений и т. Д.

Мне не нужно много PHP в моих взглядах вообще, потому что дизайнеры начинают смущаться, когда сноуборд или что-то в этом роде, когда он видит что-то вроде этого:

 <a href="<?php $img="$img_server/$row['pic'].png"; echo $img; ?>"> <img src="<?php echo $img; ?>" /></a> 

Это достаточно сложно для программиста. Средний графический дизайнер никуда не денется. С этим гораздо легче справиться:

 <a href="{$img}"><img src="{$img}" /></a> 

Программист сохранил свой неприятный код из html, и теперь дизайнер может работать над своей магией дизайна. Ура!

Быстрое обновление

Принимая во внимание все советы, я думаю, что предварительная обработка файлов – это путь, а промежуточные файлы должны быть как можно ближе к обычной «php templating», при этом шаблоны являются синтаксическим сахаром. Eval все еще на месте пока я играю с ним. Поведение heredoc несколько изменило его роль. Я напишу позже и попытаюсь ответить на некоторые из ответов, но пока …

 <?php class HereTemplate { static $loops; public function __construct () { $loops=array(); } public function passthrough ($v) { return $v; } public function parse_markup ($markup, $no_escape=null, $vars=array()) { extract($vars); $eot='_EOT_'.rand(1,999999).'_EOT_'; $do='passthrough'; if (!$no_escape) $markup=preg_replace( array( '#{?{each.*(\$\w*).*(\$\w*).*(\$\w*).*}}?#', '#{?{each.*(\$\w*).*(\$\w*).*}}?#', '#{?{each}}?#', '#{{#', '#}}#', '#{_#', '#_}#', ), array( "<?php foreach (\\1 as \\2=>\\3) { ?>", "<?php foreach (\\1 as \\2) { ?>", "<?php } ?>", "<?php echo <<<$eot\n{\$this->passthrough(", ")}\n$eot\n ?>", "<?php ", " ?>", ), $markup); ob_start(); eval(" ?>$markup<?php "); echo $markup; return ob_get_clean(); } public function parse_file ($file) { // include $file; return $this->parse_markup(file_get_contents($file)); } } // test stuff $ht = new HereTemplate(); echo $ht->parse_file($argv[1]); ?> 

 <html> {{each $_SERVER $key $value} <div id="{{$key}}"> {{!print_r($value)}} </div> {each}} </html> 

PHP сам по себе был предназначен как язык шаблонов (т. Е. Простой способ разрешить вам встраивать код внутри HTML).

Как вы видите из ваших собственных примеров, это слишком сложно, чтобы оправдать использование таким образом большую часть времени, поэтому хорошая практика отошла от этого, чтобы использовать его больше как традиционный язык и только вырваться из <?php ?> как можно меньше.

Проблема заключалась в том, что люди все еще хотели использовать язык шаблонов, поэтому были созданы платформы, такие как Smarty. Но если вы посмотрите на них сейчас, Smarty поддерживает такие вещи, как свои собственные переменные и петли foreach … и вскоре шаблоны Smarty начинают иметь те же проблемы, что и используемые шаблоны PHP; вы, возможно, просто использовали родной PHP в первую очередь.

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

Я собираюсь сделать что-то глупое и предложить что-то, что вообще не требует шаблонизационного движка, и требует только не более 5 символов за переменную / вызов, чем то, что у вас там, – замените {$foo} на <?=$foo?> затем вы можете использовать include для всех ваших потребностей в шаблонах

Если все, что вам нужно, это замена переменных, хотя это функция шаблонов, которую я фактически использую:

 function fillTemplate($tplName,$tplVars){ $tpl=file_get_contents("tplDir/".$tplName); foreach($tplVars as $k=>$v){ $tpl = preg_replace('/{'.preg_quote($k).'}/',$v,$tpl); } return $tpl; } 

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

Если вы не хотите использовать большие движки шаблонов, такие как Twig (что я искренне рекомендую), вы все равно можете получить хорошие результаты с небольшим кодом.

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

Итак, основная идея:

 function renderTemplate($templateName, $templateVars) { $templateLocation = 'tpl/' . $templateName . '.php'; $cacheLocation = 'tplCache/' . $templateName . '.php'; if (!file_exists($cacheLocation) || filemtime($cacheLocation) < filemtime($templateLocation)) { // compile template and save to cache location } // extract template variables ($templateVars['a'] => $a) extract($templateVars); // run template include 'tplCache/' . $templateName . '.php'; } 

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

Итак, давайте поговорим о компиляции. Мы определим два синтаксиса: для вывода и для структур управления. Вывод всегда экранируется по умолчанию. Если вы не хотите этого избежать, вы должны отметить его как «безопасный». Это дает дополнительную безопасность. Итак, вот пример нашего синтаксиса:

 {% foreach ($posts as $post): } <h1>{ $post->name }</h1> <p>{ $post->body }</p> {!! $post->link } {% endforeach; } 

Итак, вы используете { something } чтобы убежать и что-то эха. Вы используете {!! something} {!! something} чтобы непосредственно что-то повторять, не ускользая от него. И вы используете {% command } чтобы выполнить некоторый бит PHP-кода, не повторяя его (например, для структур управления).

Итак, вот код компиляции:

 $code = file_get_contents($templateLocation); $code = preg_replace('~\{\s*(.+?)\s*\}~', '<?php echo htmlspecialchars($1, ENT_QUOTES) ?>', $code); $code = preg_replace('~\{!!\s*(.+?)\s*\}~', '<?php echo $1 ?>', $code); $code = preg_replace('~\{%\s*(.+?)\s*\}~', '<?php $1 ?>', $code); file_put_contents($cacheLocation, $code); 

Вот и все. Вы, однако, должны отметить, что это больше подвержено ошибкам, чем реальный механизм шаблонов. Но это будет работать в большинстве случаев. Кроме того, обратите внимание, что это позволяет писателю шаблона выполнить произвольный код. Это и про и кон.

Итак, вот весь код:

 function renderTemplate($templateName, $templateVars) { $templateLocation = 'tpl/' . $templateName . '.php'; $cacheLocation = 'tplCache/' . $templateName . '.php'; if (!file_exists($cacheLocation) || filemtime($cacheLocation) < filemtime($templateLocation)) { $code = file_get_contents($templateLocation); $code = preg_replace('~\{\s*(.+?)\s*\}~', '<?php echo htmlspecialchars($1, ENT_QUOTES) ?>', $code); $code = preg_replace('~\{!!\s*(.+?)\s*\}~', '<?php echo $1 ?>', $code); $code = preg_replace('~\{%\s*(.+?)\s*\}~', '<?php $1 ?>', $code); file_put_contents($cacheLocation, $code); } // extract template variables ($templateVars['a'] => $a) extract($templateVars, EXTR_SKIP); // run template include 'tplCache/' . $templateName . '.php'; } 

Я не тестировал вышеуказанный код;) Это только основная идея.

Окончательного решения нет. У каждого есть плюсы и минусы. Но вы уже пришли к выводу, чего хотите. И это кажется очень разумным направлением. Поэтому я предлагаю вам найти наиболее эффективный способ его достижения.

В основном вам нужно только приложить свои документы в синтаксическом сахаре heredoc. В начале каждого файла:

 <?=<<<EOF 

И в конце каждого файла шаблона:

 EOF; ?> 

Награда за достижение. Но, очевидно, это смущает большинство синтаксических движков подсветки. Я мог бы исправить свой текстовый редактор, это с открытым исходным кодом. Но Dreamweaver – это совсем другое дело. Поэтому единственным полезным вариантом является использование небольшого сценария предварительного компилятора, который может конвертировать между шаблонами с необработанными $ varnames-HTML и шаблонами, заключенными в Heredoc. Это очень простой подход к regex и перезаписи файлов:

 #!/usr/bin/php -Cq <?php foreach (glob("*.tpl") as $fn) { $file = file_get_contents($fn); if (preg_match("/<\?.+<<</m")) { // remove $file = preg_replace("/<\?(=|php\s+print)\s*<<<\s*EOF\s*|\s+EOF;\s*\?>\s*/m", "", $file); } else { // add heredoc wrapper $file = "<?php print <<<EOF\n" . trim($file) . "\nEOF;\n?>"; } file_put_contents($fn, $file); } ?> 

Это заданное – где-то вам понадобятся шаблоны с небольшим количеством логики if-else. Поэтому для последовательной обработки вам необходимо, чтобы все шаблоны действовали как правильный PHP без специальной обертки обработки eval / regex. Это позволяет вам легко переключаться между шаблонами heredoc, но также иметь несколько с обычным выходом <?php print . Смешивайте и сопоставляйте по мере необходимости, и дизайнеры могут работать с большинством файлов, но избегать нескольких сложных случаев. Для примера для моих шаблонов я часто использую только:

 include(template("index")); // works for heredoc & normal php templ 

Нет дополнительного обработчика и работает как с общими типами шаблонов (raw php и smartyish html-файлы). Единственным недостатком является случайное использование сценария конвертера.

Я также добавил бы extract(array_map("htmlspecialchars",get_defined_vars())); поверх каждого шаблона для обеспечения безопасности.

Во всяком случае, ваш метод passthrough является исключительно умным, я должен сказать. Я бы назвал псевдоним heredoc $php , поэтому $_ все еще доступен для gettext.

 <a href="calc.html">{$php(1+5+7*3)}</a> is more readable than Smarty 

Я думаю, что сам собираюсь принять этот трюк.

 <div>{$php(include(template($ifelse ? "if.tpl" : "else.tpl")))}</div> 

Немного растягивает, но, кажется, после всех возможных есть простая логика в шаблонах heredoc. Может привести к шаблону-fileritis, но помогает обеспечить наиболее простой шаблон логики.

Offtopic: Если три <<<heredoc&EOF; синтаксические линии все еще кажутся слишком грязными, тогда лучший вариант no-eval использует парсер, основанный на регулярном выражении. Я не согласен с распространенным мифом о том, что это медленнее, чем собственный PHP. На самом деле я считаю, что токенизатор PHP и парсер отстают от PCRE. Особенно, если речь идет только об интерполирующих переменных. Просто последнее не является APC / Zend-кэшем, вы сами по себе будете там.

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

Лично я использую этот механизм шаблонов: http://articles.sitepoint.com/article/beyond-template-engine/5

Мне это очень нравится, особенно потому, что это простота. Это похоже на вашу последнюю инкарнацию, но IMHO – лучший подход, чем использование heredoc и создание еще одного слоя синтаксического анализа над PHP. Также нет eval (), но буферизация вывода и скопированные переменные шаблона. Используйте следующее:

 <?php require_once('template.php'); // Create a template object for the outer template and set its variables. $tpl = new Template('./templates/'); $tpl->set('title', 'User List'); // Create a template object for the inner template and set its variables. // The fetch_user_list() function simply returns an array of users. $body = new Template('./templates/'); $body->set('user_list', fetch_user_list()); // Set the fetched template of the inner template to the 'body' variable // in the outer template. $tpl->set('body', $body->fetch('user_list.tpl.php')); // Echo the results. echo $tpl->fetch('index.tpl.php'); ?> 

Шаблон внешнего шаблона будет выглядеть так:

 <html> <head> <title><?=$title;?></title> </head> <body> <h2><?=$title;?></h2> <?=$body;?> </body> </html> 

и внутренний (входит в переменную $body body шаблона outter) следующим образом:

 <table> <tr> <th>Id</th> <th>Name</th> <th>Email</th> <th>Banned</th> </tr> <? foreach($user_list as $user): ?> <tr> <td align="center"><?=$user['id'];?></td> <td><?=$user['name'];?></td> <td><a href="mailto:<?=$user['email'];?>"><?=$user['email'];?></a></td> <td align="center"><?=($user['banned'] ? 'X' : '&nbsp;');?></td> </tr> <? endforeach; ?> </table> - <table> <tr> <th>Id</th> <th>Name</th> <th>Email</th> <th>Banned</th> </tr> <? foreach($user_list as $user): ?> <tr> <td align="center"><?=$user['id'];?></td> <td><?=$user['name'];?></td> <td><a href="mailto:<?=$user['email'];?>"><?=$user['email'];?></a></td> <td align="center"><?=($user['banned'] ? 'X' : '&nbsp;');?></td> </tr> <? endforeach; ?> </table> 

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

Dead-simple templating с использованием функции:

 <?php function template($color) { $template = <<< ENDTEMPLATE The colors I like are {$color} and purple. ENDTEMPLATE; return $template . "\n"; } $color = 'blue'; echo template($color); $color = 'turquoise'; echo template($color); 

Эти результаты:

 The colors I like are blue and purple. The colors I like are turquoise and purple. 

Ничего необычного, но он работает с использованием стандартного PHP без расширений. Кроме того, использование функций для инкапсуляции шаблонов должно помочь с надлежащим разделением MVC. Также (и это то, что мне нужно для моего кодирования сегодня). Я могу сохранить заполненный шаблон для вывода в файл (позже в моей программе).

Это минимальная реализация усов, чтобы просто заменить переменные.

 // Example: // miniMustache( // "{{documentName }} - pag {{ page.current }} / {{ page.total }}", // array( // 'documentName' => 'YourCompany Homepage', // 'page' => array('current' => 1, 'total' => 10) // ) // ) // // Render: "YourCompany Homepage - pag 1 / 10" function miniMustache($tmpl, $vars){ return preg_replace_callback( '/\{\{([A-z0-9_\.\s]+)\}\}/', function ($matches) use ($vars) { //Remove white spaces and split by "." $var = explode('.',preg_replace('/[\s]/', '', $matches[1])); $value = $vars; foreach($var as $el){ $value = $value[$el]; } return $value; }, $tmpl); } 

В некоторых случаях этого более чем достаточно. Если вам нужна полная мощность: https://github.com/bobthecow/mustache.php