Background
Yani, last time Ben gibi tepkiler çok aldım, PHP şablonları hakkında sordu:
- gerekli değildir; PHP kendi başına yeterince iyi bir çiftleşmiş dilidir.
- tasarımcılar (veya civarında) ile çalışmak için güçlü ve kolay hem de bir çiftleşmiş dili geliştirmek zor.
- Zaten, kullanım çiftleşmiş çerçeve X. yapildi
- sen aptalsın.
Bu noktaların hepsi geçerlilik miktar var. Bunları akılda tutarak, ben çiftleşmiş şeyle devam etti, ve şimdi daha fazla soru ile geri döndüm. :)
Overview
Goals
İşte bu çiftleşmiş motoru için hedefleri şunlardır:
- minimal sözdizimi.
- temiz php kod üretmek.
- html dizim bozmazlar.
- php geliştiricileri yeni bir şey (iyi, çok değil) öğrenmek gerek.
- php akış kontrolü çoğu destek (her şey ama yapmak .. iken).
- inline php desteği.
Umarım bu oldukça iyi geliyor. O not hedefleri arasında fark ya da "X yapıyor şablon yazarlar önlemek" gibi şeyler vardır "şablonları anonim kullanıcılar tarafından temin edilecektir." Güvenlik burada not büyük bir endişe, bu normal olmayan bir şablonu php dosyasında olacağını daha başka olduğunu.
Rules
- default escape sequence is
{{...}}
.*- if no other rules match, echo or evaluate the sequence
- dizisi, bir noktalı virgül ile sona ererse, tüm dizisini değerlendirmek
- aksi halde, ilk ifade yankı ve geri kalan değerlendirmek
- if no other rules match, echo or evaluate the sequence
{{for|foreach|if|switch|while (...):}}
begins a block.- durumda parantez atlanabilir
- kolon atlanabilir
- Dış sağ dirseği dirsek eşleştirme için ihmal edilebilir. **
{{else|elseif|break|continue|case|default}}
do what you'd expect.- durumda parantez atlanabilir
- Dış sağ dirseği dirsek eşleştirme için
{{case}}
üzerine atlanabilir. - Dış sol dirseği dirsek eşleştirme için
{{break|continue}}
üzerine atlanabilir.
{{end}}
ends a block.- kelime karakterler, 'sonuna' kadar eklenebilir mesela 'End_if'
- Dış sol dirseği dirsek eşleştirme için ihmal edilebilir.
* custom brackets can be used.
** bracket matching syntax can be disabled.
Templating
Şimdiye kadar biz gerçekten sadece <?php...?>
ve <?=...?>
için bir yedek sözdizimi ile geldim. Bu gerçekten yararlı olabilmesi için, bazı çiftleşmiş-özel operasyon gerekir.
Ben çalıştığım bir diğer çiftleşmiş çerçeve de burada çalışması gerektiğini basit bir kap / içerik paradigma kullanır. Bu şablon sistemi xml tabanlı, bu yüzden kod böyle bir şey olmazdı ...
<!-- 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>
Aynı isimde bir içerik alanının birden fazla deklare önceki içeriğini değiştirir, ancak önceki içeriği kadar, Konteyner yoluyla İçerik etiketi içinde geçerli olacaktır:
<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" />
Should çıkışı:
<script src="bar.js"></script>
<script src="foo.js"></script>
I set
ve bu yeni çiftleşmiş sistemde get
etiketleri ile Content
ve Container
yeniden denedim. Bunlar tabii, onlar xml etiketleri değil, dışında tam olarak aynı şekilde çalışması için tasarlanmıştır ediyoruz.
Code
Lafı daha fazla uzatmadan:
<?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';
}
}
?>
Örnek şablonu 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>
Questions
Ben bu şeyle devam etmek hakkında bir kaç soru var. Bazı kesin cevaplar var, biraz daha CW malzeme olabilir ...
set / get dağınık kod üretir. Geliştirilebilir mi? Ben set arasında mantıklı orta yere çeşit arıyorum / almak ve
{{function}}
(kod ve örneğe bakın).Ne bu popüler çiftleşmiş dilde sunulan neler eksik?
Sözdizimi Tamam mı? Şeyler, bir şeyler yapmak çizgileri ve akış kontrol hatları yankı hatları daha sözdizimsel farklı olmalıdır? Nasıl eşleşen için opsiyonel dış destekleri hakkında ... aptalca?
Bu konuda herkesin girişini haber bekliyorum.