肮脏的简单PHP模板...这可以在没有“eval”的情况下工作吗?

2022-08-31 00:03:46

更新-感谢您的所有回复。这个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 已经非常擅长模板化了。

模板化的一些快速原因很有用:

  • 您可以控制它

    如果在文件转到解释器之前对其进行预处理,则可以更好地控制它。您可以注入内容,锁定权限,抓取恶意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模板化”,模板是语法糖。Eval现在仍然在我玩的时候。赫雷多克的东西已经改变了它的角色。我稍后会写更多,并试图回应一些答案,但现在......

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

答案 1

PHP本身最初是一种模板语言(即一种允许您在HTML中嵌入代码的简单方法)。

正如你从你自己的例子中看到的,它变得太复杂了,以至于大多数时候都无法证明以这种方式使用是合理的,所以好的做法从这种方式转移到更多地将其用作传统语言,并且尽可能少地打破标签。<?php ?>

问题在于,人们仍然想要一种模板语言,所以像Smarty这样的平台被发明出来了。但是如果你现在看看它们,Smarty支持像它自己的变量和foreach循环这样的东西......不久之后,Smarty模板开始出现与PHP模板相同的问题;你也可以一开始就使用原生PHP。

我在这里想说的是,一个简单的模板语言的理想实际上并不容易正确理解。几乎不可能让它既简单又不吓跑设计师,同时又给它足够的灵活性,让它真正做你需要它做的事情。


答案 2

如果你不打算使用像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; }

所以,你用来逃避和呼应一些东西。你习惯于直接回显某些东西,而不是逃避它。而且你用来执行一些PHP代码而不回显它(例如,对于控制结构)。{ something }{!! something}{% command }

所以,这是编译代码:

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

我还没有测试上面的代码;)这只是基本的想法。


推荐