Грязно-простые шаблоны PHP ... это может работать без `eval`? - PullRequest
16 голосов
/ 14 октября 2010

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


Я составлял быстрый скрипт для друга и наткнулся на очень простой способ создания шаблонов в 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

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

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

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

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

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

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

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

<?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>

Ответы [ 8 ]

24 голосов
/ 18 октября 2010

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

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

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

9 голосов
/ 17 октября 2010

Если вы не хотите использовать большие движки шаблонов, такие как 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} для непосредственного отображения чего-либо, без экранирования. И вы используете {% 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';
}

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

9 голосов
/ 14 октября 2010

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

3 голосов
/ 17 октября 2010

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

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

<?=<<<EOF

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

EOF;
?>

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

#!/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

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

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

2 голосов
/ 18 октября 2010

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

0 голосов
/ 21 апреля 2015

Это минимальная реализация усов для простой замены переменных.

// 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

0 голосов
/ 22 мая 2013

Простой простой шаблонизатор с использованием функции:

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

0 голосов
/ 23 октября 2010

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

Мне очень это нравится, особенно из-за его простоты.Это похоже на ваше последнее воплощение, но ИМХО лучший подход, чем использование 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 шаблона внешнего элемента) примерно так:

<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>

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

...