代码之家  ›  专栏  ›  技术社区  ›  Dagg Nabbit

非常简单的PHP模板…如果没有eval,它能工作吗?

  •  16
  • Dagg Nabbit  · 技术社区  · 14 年前

    谢谢你的回复。这个Q有点乱了,所以我开始 sequel 如果有人感兴趣的话。


    我正在为一个朋友编写一个快速脚本,偶然发现了一个用PHP编写模板的非常简单的方法。

    passthrough函数允许表达式求值以及字符串中的函数和静态方法调用:

    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

    问题

    • 有人能想出一种不使用 评估 ,但没有添加解析器或大量的regex疯狂行为?


    <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 ?> 标记在您的视图中,很容易变得懒惰并执行一些数据库查询或执行其他服务器操作。使用像上面这样的方法,每个“块”(没有分号)只能使用一个语句,因此很难陷入这个陷阱。 <?= ... ?>

    • 短标记并不总是启用的

      …我们希望我们的应用程序能够在各种配置下运行。

    当我最初把一个概念组合起来时,它以一个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>
    

    考虑到每个人的建议,我认为对文件进行预处理是可行的,中间文件应该尽可能接近普通的“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>
    
    8 回复  |  直到 7 年前
        1
  •  26
  •   mensi    12 年前

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

    <?php ?> 标签尽可能少。

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

    我想说的是,简单模板语言的理想实际上并不是那么容易实现的。它实际上是不可能做到既简单又不吓跑设计师,同时又给它足够的灵活性来实际做你需要它做的事情。

        2
  •  10
  •   tobyodavies    14 年前

    我将做一些愚蠢的事情,并建议一些根本不需要模板引擎,并且每个变量/调用最多只需要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;
    }
    

        3
  •  10
  •   NikiC    13 年前

    如果你不习惯使用像 Twig (我真诚地推荐)你仍然可以用很少的代码获得好的结果。

    所以,基本思想是:

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

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

        4
  •  3
  •   mario    14 年前

    没有最终的解决办法。各有利弊。但你已经完成了你想要的。这似乎是一个非常明智的方向。所以我建议你找到最有效的方法来实现它。

    <?=<<<EOF
    

    在每个模板文件的末尾:

    EOF;
    ?>
    

    成就奖。但很明显,这混淆了大多数语法高亮引擎。我可以修复我的文本编辑器,它是开源的。但Dreamweaver是另一回事。因此,唯一有用的选择是使用一个小的预编译器脚本,该脚本可以在带有原始$varnames HTML和herdoc封闭模板的模板之间进行转换。这是一种非常基本的正则表达式和文件重写方法:

    #!/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);
    }
    ?>
    

    <?php print 输出。适当地混合和匹配,设计者可以处理大多数文件,但可以避免少数复杂的情况。例如,我经常使用的模板是:

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

    不需要额外的处理程序,并且适用于两种常见的模板类型(原始php和smartyishhtml文件)。唯一的缺点是偶尔使用上述转换脚本。

    我还要加一个 extract(array_map("htmlspecialchars",get_defined_vars())); 在每个模板上的安全性。

    不管怎样,你的 passthrough 我不得不说,方法非常聪明。我会叫埃雷多克的别名 $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&EOF; 语法行仍然 出现 唯一地 关于插值变量。只是后者不是APC/Zend缓存的,你可以自己去那里。

        5
  •  2
  •   Tgr    14 年前

        6
  •  0
  •   DanMan    14 年前

    我个人使用的是这个模板引擎: 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');   
    ?>
    

    outter模板如下所示:

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

    如果你不喜欢/不能使用短标签,那就用回声代替它们。这是非常简单的,你可以得到,但仍然有所有的功能,你将需要IMHO。

        7
  •  0
  •   Mark Leighton Fisher    11 年前

    <?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分离。另外(这也是我今天编码所需要的)我可以将填写好的模板保存到一个文件中(稍后在我的程序中)。

        8
  •  0
  •   Chris Cinelli    9 年前

    这是mustache的一个最小实现,只需替换变量。

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