简单的PHP模板,继续

Dag*_*bit 0 php regex templates

背景


所以,上次我询问PHP模板时,我得到了很多回复,比如:

  • 它不是必需的; PHP本身就是一个很好的模板语言.
  • 很难开发出一种既有力又易于设计师使用(或周围)的模板语言.
  • 它已经完成,使用模板框架X.
  • 你真蠢.

所有这些要点都具有一定的有效性.记住它们,我继续进行模板化的事情,现在我回来了更多的问题.:)

概观


目标

以下是此模板引擎的目标:

  • 最小的语法.
  • 生成干净的PHP代码.
  • 不要破坏html语法高亮.
  • 不需要php开发人员学习任何新东西(好吧,不多).
  • 支持大多数php流量控制(除了do..while之外).
  • 支持内联php.

希望听起来不错.请注意,目标中没有 "阻止模板作者执行X "或"模板将由匿名用户提供".安全性不是这里的主要问题,不仅仅是普通的非模板化php文件.

规则

  • 默认转义序列是{{...}}.*
    • 如果没有其他规则匹配,则回显或评估序列
      • 如果序列以分号结束,则评估整个序列
      • 否则,回显第一个表达式并评估其余表达式
  • {{for|foreach|if|switch|while (...):}} 开始一个街区.
    • 条件中的括号可以省略
    • 冒号可以省略
    • 可以省略右外支架以进行支架匹配.**
  • {{else|elseif|break|continue|case|default}} 做你期望的事.
    • 条件中的括号可以省略
    • 可以省略外右支架以{{case}}进行支架匹配.
    • 左支架可以省略{{break|continue}}用于支架匹配.
  • {{end}} 结束一个街区.
    • 单词字符可以附加到'end',例如'end_if'
    • 可以省略左外括号以进行括号匹配.

*可以使用自定义括号.
**可以禁用括号匹配语法.

模板

到目前为止,我们真的只是想出了一个替代语法<?php...?><?=...?>.为了真正有用,我们需要一些模板特定的操作.

我工作的另一个模板框架使用了一个简单的容器/内容范例,在这里应该很好用.该模板系统是基于xml的,因此代码看起来像这样......

<!-- in a template -->
<html>
  <head>
    <tt:Container name="script" />
  </head>
  <body>
    <tt:Container name="main" />
  </body>
</html>

<!-- in a page -->
<tt:Content name="script">
  <script src="foo.js"></script>
</tt:Content>
<tt:Content name="main">
  <div>...</div>
</tt:Content>
Run Code Online (Sandbox Code Playgroud)

具有相同名称的内容区域的多个声明将替换先前的内容,但之前的内容将通过Container在Content标签中可用,因此:

<tt:Content name="script">
  <script src="foo.js"></script>
</tt:Content>
...
<tt:Content name="script">
  <script src="bar.js"></script>
  <tt:Container name="script" />
</tt:Content>
...
<tt:Container name="script" />
Run Code Online (Sandbox Code Playgroud)

应输出:

  <script src="bar.js"></script>
  <script src="foo.js"></script>
Run Code Online (Sandbox Code Playgroud)

我试图在这个新的模板系统中重新创建ContentContainer通过setget标记.它们的工作方式完全相同,当然,它们不是xml标签.


无需再费周折:

<?php

class Detemplate {

  public  $container_prefix='_tpl_';
  public  $brackets='{}';
  public  $bracket_matching=true;
  public  $use_cache=false;

  private $block_keywords=array('for','foreach','if','switch','while');
  private $set_count;
  private $get_count;

  public function parse_file ($file, $vars=array()) {
    $sha1=sha1($file);
    $cache = dirname(__FILE__)."/cache/".basename($file).".$sha1.php";
    $f = "{$this->container_prefix}page_{$sha1}_";
    if (!$this->use_cache || !file_exists($cache) || filemtime($cache)<filemtime($file)) {
      $php =  "<?php function $f {$this->t_vars()} ?>".
              $this->parse_markup(file_get_contents($file)).
              "<?php } ?>";
      file_put_contents($cache, $php);
    }
    include $cache;
    $f($vars);
  }

  public function parse_markup ($markup) {

    $blocks=implode('|', $this->block_keywords);

    $arglist= '\s*[\s(](.*?)\)?\s*';  // capture an argument list
    $word=    '\s*(\w+)\s*';          // capture a single word

    $l='\\'.$this->brackets{0}; // left bracket
    $r='\\'.$this->brackets{1}; // right bracket

    $dl="#$l$l";
    $sl=$this->bracket_matching ? "#$l?$l" : $dl;
    $dr="$r$r(?!:$r)#"; 
    $sr=$this->bracket_matching ? "$r$r?(?!:$r)#" : $dr; 

    $markup=preg_replace_callback(
      array (
        $sl.'(end)[_\w]*\s*;?\s*'.$dr,
        $dl.'(el)se\s*if'.$arglist.':?\s*'.$dr,
        $dl.'(else)\s*:?\s*'.$dr,
        $dl.'(case)'.$word.':?\s*'.$sr, 
        $dl.'(default)()\s*:?\s*'.$sr,
        $sl.'(break|continue)\s*;?\s*'.$dr,
        $dl.'(set)'.$word.':?\s*'.$sr, 
        $dl.'(get)'.$word.':?\s*'.$dr, 
        $dl.'(parse)'.$word.':?\s*'.$dr, 
        $dl.'(function|fn)'.$word.$arglist.':?\s*'.$sr,
        $dl.'('.$blocks.')'.$arglist.':?\s*'.$sr,
        '#('.$l.$l.')(.+?)(;?)\s*'.$dr, 
        '#\s*(\?)>[\s\n]*<\?php\s*#',
      ),
      array($this, 'preg_callback'),
      $markup);

    return $markup;

  }

  private function preg_callback ($m) {

    switch ($m[1]) {

      // end of block

      case "end":
        return "<?php } } ?>";

      // keywords with special handling

      case "el": // elseif
        return "<?php } elseif ({$m[2]}) { ?>";
      case "else":
        return "<?php } else { ?>";

      case "case": case "default":
         return "<?php {$m[1]} {$m[2]}: ?>";

      case "break": case "continue":
        return "<?php {$m[1]}; ?>";

      // parse an external template document

      case "parse":
        return $this->parse_markup(file_get_contents($m[2]));

      // save / load content sections

      case "set":
        $i=++$this->set_count[$m[2]];
        $f=$this->t_fn($m[2], $i);
        $p=$this->t_fn($m[2], $i-1);
        $v=$this->t_fn_alias($m[2]);
        return  "<?php if (!function_exists('$f')) { $v='$f'; ".
          "function $f {$this->t_vars()} unset ($v); $v='$p'; ?>";

      case "get":
        $i=++$this->get_count[$m[2]];
        $c=$this->t_fn_ctx($m[2], $i);
        $v=$this->t_tmp();
        $a=$this->t_fn_alias($m[2]);
        return  "<?php if (!$c) { ".
          "foreach (array_keys(get_defined_vars()) as $v) $c".  
          "[$v]=&\$$v; unset($v); } $a(&$c); ?>";


      case "function": case "fn":
        return "<?php if (!function_exists('{$m[2]}')) { ".
          "function {$m[2]} ({$m[3]}) { ?>";

      // echo / interpret

      case "{{":
        return "<?php".($m[3]?"":" echo")." {$m[2]}; ?>";

      // merge adjacent php tags

      case "?": 
        return " ";
    }

    // block keywords
    if (in_array($m[1], $this->block_keywords)) {
      return "<?php { {$m[1]} ({$m[2]}) { ?>";
    }

  } 

  private function t_fn ($name, $index) {
    if ($index<1) return "is_null";
    return "{$this->container_prefix}{$name}_$index";
  }

  private function t_fn_alias ($name) {
    return "\${$this->container_prefix}['fn_$name']";
  }

  private function t_fn_ctx ($name, $index) {
    return "\${$this->container_prefix}['ctx_{$name}_$index']";
  }

  private function t_vars () {
      $v=$this->t_tmp();
      return "($v) { extract($v); unset($v);";
  }

  private function t_tmp () {
      return '$'.$this->container_prefix.'v';
  }


}

?>
Run Code Online (Sandbox Code Playgroud)

示例模板化html:

<script>var _lang = {{json_encode($lang)}};</script>
<script src='/cartel/static/inventory.js'></script>
<link href='/cartel/static/inventory.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='#{{urlencode($lang['T_FILTER_ALL'])}}' onclick='applyFilter();'>{{$lang['T_FILTER_ALL']}}</a>
  {{foreach ($filters as $f)}}
    <a href='#{{urlencode($f)}}' onclick='applyFilter("c_{{urlencode($f)}}");'>{{$f}}</a>
  {{end}}
  </div>

  <table class="inventory" id="inventory_table">  

  {{foreach $row_array as $row_num=>$r}

    {{if $row_num==0}

    <tr class='static'>
      {{foreach $r as $col}
      <th>{{$col}}</th>
      {end}} 
      <th class='ordercol'>{{$lang['T_ORDER']}}</th>
    </tr>

    {{else}}


    {{function spin_button $id, $dir, $max}
    <a href='#' class='spinbutton' 
       onclick="return spin('{{$id}}', {{$dir}}, {{$max}})">
      {{$dir==-1 ? '&#x25C0;' : '&#x25B6;'}}
    </a>
    {end}}

    <tr class="{{'c_'.urlencode($r[$man_col])}}">
      {{foreach $r as $i=>$col}
      <td class='{{$i?"col":"firstcol"}}'>{{$col}}</td>
      {end}}
      <td class='ordercol'>
        {{$id="part_{$r[$part_col]}"; $max=$r[$qty_col];}}
        {{spin_button($id, -1, $max)}}
        <input  onchange="spin(this.id, 0, '{{$max}}')" 
                id='{{$id}}' name='{{$id}}'type='text' value='0' />
        {{spin_button($id, +1, $max)}}
      </td>
    </tr>

    {end}}


  {end}}

    <tr class="static"><th colspan="{{$cols+1}}">{{$lang['T_FORM_HELP']}}</th></tr>

  {{foreach $fields as $f}

    <tr class="static">
      <td class="fields" colspan="2">
        <label for="{{$f[0]}}">{{$f[1]}}</label>
      </td>
      <td class="fields" colspan="{{$cols-1}}">
          <input name="{{$f[0]}}" id="{{$f[0]}}" type="text" />
      </td>
    </tr>

  {end}}

    <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>
Run Code Online (Sandbox Code Playgroud)

问题


我有一些关于如何继续这件事的问题.有些人有明确的答案,有些可能是CW材料......

  • set/get产生凌乱的代码.可以改进吗?我正在寻找set/get和{{function}}(参见代码和示例)之间的某种明智的中间立场.

  • 在流行的模板语言中提供了什么?

  • 语法好吗?回应事物的线条,做事情的线条和流量控制线条在语法上会有更多不同吗?可选的外支架如何匹配...傻?

期待听到大家对此的意见.

cee*_*yoz 5

最小的语法.

<?=$variable?>http://phptemplatinglanguage.com/

生成干净的PHP代码.

这对我来说看起来不太干净.

不要破坏html语法高亮.

你破坏了PHP语法高亮,我发现比破坏HTML语法高亮更有问题.如果你有一个更好的编辑器,了解PHP和HTML如何交互(我使用Textmate),这甚至不是一个问题.

不需要php开发人员学习任何新东西(好吧,不多).

普通PHP已经符合资格.

支持大多数php流量控制(除了do..while之外).

普通PHP支持所有PHP流控制.

支持内联php.

普通的PHP支持内联PHP.

总之,我认为这种方法没有任何好处,当然也不会超过成熟的现有PHP框架和模板引擎.

  • @mario,我认为他们非常有帮助.为什么要在模板引擎上编写模板引擎?为何停在那里?为什么不在php之上创建一个模板引擎? (3认同)
  • 你没有回答他的任何问题,你反驳他的目标. (2认同)
  • @mario"我有一些关于如何处理这件事的问题"和"期待听到每个人对此的意见".我对这两个组成部分的回答是"不要"和"亲爱的上帝". (2认同)