Итак, в прошлый раз , когда я спросил о шаблонах PHP, у меня появилось много ответов вроде:
Все эти моменты имеют для них некоторую ценность. Помня их, я продолжил работу с шаблонами, и теперь у меня появилось больше вопросов. 🙂
Вот цели для этого шаблонизатора:
Надеюсь, это звучит неплохо. Обратите внимание, что среди целей нет таких вещей, как «предотвращать создание авторов шаблонов» или «шаблоны будут предоставляться анонимными пользователями». Безопасность здесь не является серьезной проблемой, не более, чем в обычном не templated php-файле.
{{...}}
. *
{{for|foreach|if|switch|while (...):}}
начинает блок.
{{else|elseif|break|continue|case|default}}
делать то, что вы ожидаете.
{{case}}
для сопоставления скобок. {{break|continue}}
для сопоставления скобок. {{end}}
завершает блок.
* Пользовательские скобки могут использоваться.
** Синтаксис сопоставления кронштейнов может быть отключен.
До сих пор мы действительно придумали синтаксис замены для <?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>
Несколько объявлений области содержимого с тем же именем заменят предыдущий контент, но предыдущий контент будет доступен в теге Content через Container, поэтому:
<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" />
Должен выводиться:
<script src="bar.js"></script> <script src="foo.js"></script>
Я попытался воссоздать Content
и Container
через set
и get
теги в этой новой системе шаблонов. Они предназначены для работы точно так же, за исключением, конечно, не тегов 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'; } } ?>
,<?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'; } } ?>
по<?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'; } } ?>
Пример шаблона 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 ? '◀' : '▶'}} </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}}"> </td> <td colspan="1" class="send"><input type="submit" value="{{$lang['T_SEND']}}" /></td> </tr> </table> </form>
У меня есть несколько вопросов о том, как это сделать. У некоторых есть определенные ответы, некоторые могут быть больше материала CW …
set / get создает беспорядочный код. Можно ли улучшить? Я ищу какую-то разумную середину между set / get и {{function}}
(см. Код и пример).
Чего не хватает на популярных языках шаблонов?
Синтаксис ок? Должны ли строки, которые эхо-вещи, линии, которые делают вещи, и линии управления потоком, более синтаксически различны? Как насчет дополнительных внешних кронштейнов для сопоставления … глупо?
Мы с нетерпением ждем, когда все будут проинформированы об этом.
минимальный синтаксис.
<?=$variable?>
и http://phptemplatinglanguage.com/
производить чистый php-код.
Это не выглядит очень чистым для меня.
не нарушайте подсветку синтаксиса html.
Вы нарушаете подсветку синтаксиса PHP, который я нахожу более проблематичным, чем разбиение подсветки синтаксиса HTML. Если вы получите лучший редактор, который понимает, как взаимодействуют PHP и HTML (я использую Textmate), это даже не проблема.
не нужно, чтобы разработчики PHP узнали что-нибудь новое (ну, не так много).
Обычный PHP уже квалифицируется.
поддерживают большинство управления потоком php (все, кроме do..while).
Обычный PHP поддерживает весь контроль потока PHP.
поддержка встроенных php.
Обычный PHP поддерживает встроенный PHP.
Таким образом, я не вижу преимуществ для этого подхода, конечно, не для зрелых существующих фреймворков PHP и шаблонов.