更新記録とコメントはTwango Archive - ゼロと無限の間のログへどうぞ。
SymfonyのテンプレートエンジンTwigやPythonのDjangoみたいなテンプレートエンジンを、PHPの関数1つで作ってみた。
(create_function()を使ってるから関数2つではないかという指摘は置いておいて。)
名前は羽柴秀吉にあやかって、Twangoにした。
TwigやDjangoと同様に、Twangoには下記のような特徴がある。
{{ msg|trim|strToUpper }}
ではTwangoに無い機能はというと、コンテンツキャッシュ(テンプレートから変換したPHPのキャッシュだけでなく、DB等から取得したデータを埋め込んだHTMLのキャッシュ)機能が無い。それはテンプレートエンジンがやらなくてもいいと思うから。コンテンツキャッシュは、Apacheのmod_cacheなり、PEAR::Cache_Liteなり、P2_Cacheなりを使えばいいと思う。
Twangoの具体的な使い方についてはこのページの中ほどからの例を参考に。なるべくTwigに似せるようにしたけど面倒だったので微妙に違うところもある
MITライセンスで。
<?php /** * Twango * @see http://0-oo.net/sbox/php-tool-box/template-like-twig * @copyright 2010 dgbadmin@gmail.com * @license http://0-oo.net/pryn/MIT_license.txt (The MIT license) */ function twango($path, $tplValues = array(), $cacheDir = '.') { $cachePath = "$cacheDir/" . str_replace('/', '._', $path) . '.cache.php'; //テンプレート更新チェック if (!file_exists($cachePath) || filemtime($cachePath) < filemtime($path)) { //変数出力のPHP化(フィルターあり) $callback = '$tokens = explode("|", trim($matches[1]));'; $callback .= '$s = "$" . array_shift($tokens);'; $callback .= 'foreach ($tokens as $token) $s = "$token($s)";'; $callback .= 'return "<?php echo htmlSpecialChars($s, ENT_QUOTES) ?>";'; $fn = create_function('$matches', $callback); $tpl = preg_replace_callback('/\{\{(.+)\}\}/U', $fn, file_get_contents($path)); //継承のPHP化 $regexp = '/\{% extends [\'"](.+)[\'"] %\}(.*)$/s'; $extends = '<?php$2' . __FUNCTION__ . '("$1", $tplValues, $cacheDir);'; $blockStart = 'if (!function_exists("block_$1")) {'; $blockStart .= 'function block_$1($tplValues) { extract($tplValues) ?>'; $blockEnd = '<?php }}'; if (preg_match($regexp, $tpl)) { //継承の場合 $tpl = preg_replace($regexp, $extends, $tpl); } else { $blockStart = '<?php ' . $blockStart; $blockEnd .= '; block_$1($tplValues) ?>'; } //その他のPHP化 $forNum = '<?php foreach (range($2, $3) as $$1) { ?>'; $forKV = '<?php foreach ($$3 as $$1 => $$2) { ?>'; $ptn = array( '/\{% block (\w+) %\}/' => $blockStart, '/\{% endblock (\w+) %\}/' => $blockEnd, '/\{% for (\w+) in (\d+)\\.\\.(\d+) %\}/U' => $forNum, '/\{% for (\w+), ?(\w+) in (\w+) %\}/U' => $forKV, '/\{% for (\w+) in (\w+) %\}/U' => '<?php foreach ($$2 as $$1) { ?>', '/\{% if (.+) %\}/U' => '<?php if ($1) { ?>', '/\{% else *if (.+) %\}/U' => '<?php } else if ($1) { ?>', '/\{% else %\}/' => '<?php } else { ?>', '/\{% end.*%\}/' => '<?php } ?>', '/\{%(.+)%\}/sU' => '<?php$1?>', '/\{#.+#\}/sU' => '', ); $tpl = preg_replace(array_keys($ptn), array_values($ptn), $tpl); file_put_contents($cachePath, trim($tpl), LOCK_EX); } extract($tplValues); require($cachePath); }
<?php require('twango.php'); $values = array( 'title' => 'タイトル', 'msg' => '<こんにちは twango!> ', 'kbn' => 1, 'arr' => array('foo', 'bar'), ); twango('tmpl.html', $values);
tmpl.html
<html> <head> <!-- 変数の値を出力 --> <title>{{ title }}</title> </head> <body> <!-- 変数にフィルターをかけて出力 --> {{ msg|trim|strToUpper }} <hr> <!-- コメント --> {# 表示されない #} <hr> <!-- 配列のループ --> {% for str in arr %} {{ str }}, {% endfor %} <hr> <!-- ループでKEYとVALUEの両方を取り出す --> {% for key, value in arr %} {{ key }} => {{ value }}, {% endfor %} <hr> <!-- 始まりと終わりの数を指定してループ --> {% for i in 1..3 %} {{ i }}, {% endfor %} <hr> <!-- if文の条件部分はPHPそのままで --> {% if $kbn == 1 %} 1 {% elseif $kbn == 2 %} 2 {% else if $kbn == 3 %} 3 {% else %} 4 {% endif %} <hr> <!-- 普通のPHP文を埋め込める --> {% var_dump($values) %} <hr> <!-- 複数の行でもOK --> {% echo '今は'; $now = date('Y-m-d H:i:s'); %} {{ now }}ですよ <hr> <!-- ブロックごとに継承できる --> {% block test %} ここは継承 {% block test2 %} 継承の入れ子 {% endblock test2 %} 継承終わり {% endblock test %} <hr> <!-- 1行のブロックもOK --> {% block 1_line %}1行のブロック{% endblock 1_line %} </body> </html>
tmpl.html.cache.php
<html> <head> <!-- 変数の値を出力 --> <title><?php echo htmlSpecialChars($title, ENT_QUOTES) ?></title> </head> <body> <!-- 変数にフィルターをかけて出力 --> <?php echo htmlSpecialChars(strToUpper(trim($msg)), ENT_QUOTES) ?> <hr> <!-- コメント --> <hr> <!-- 配列のループ --> <?php foreach ($arr as $str) { ?> <?php echo htmlSpecialChars($str, ENT_QUOTES) ?>, <?php } ?> <hr> <!-- ループでKEYとVALUEの両方を取り出す --> <?php foreach ($arr as $key => $value) { ?> <?php echo htmlSpecialChars($key, ENT_QUOTES) ?> => <?php echo htmlSpecialChars($value, ENT_QUOTES) ?>, <?php } ?> <hr> <!-- 始まりと終わりの数を指定してループ --> <?php foreach (range(1, 3) as $i) { ?> <?php echo htmlSpecialChars($i, ENT_QUOTES) ?>, <?php } ?> <hr> <!-- if文の条件部分はPHPそのままで --> <?php if ($kbn == 1) { ?> 1 <?php } else if ($kbn == 2) { ?> 2 <?php } else if ($kbn == 3) { ?> 3 <?php } else { ?> 4 <?php } ?> <hr> <!-- 普通のPHP文を埋め込める --> <?php var_dump($values) ?> <hr> <!-- 複数の行でもOK --> <?php echo '今は'; $now = date('Y-m-d H:i:s'); ?> <?php echo htmlSpecialChars($now, ENT_QUOTES) ?>ですよ <hr> <!-- ブロックごとに継承できる --> <?php if (!function_exists("block_test")) {function block_test($tplValues) { extract($tplValues) ?> ここは継承 <?php if (!function_exists("block_test2")) {function block_test2($tplValues) { extract($tplValues) ?> 継承の入れ子 <?php }}; block_test2($tplValues) ?> 継承終わり <?php }}; block_test($tplValues) ?> <hr> <!-- 1行のブロックもOK --> <?php if (!function_exists("block_1_line")) {function block_1_line($tplValues) { extract($tplValues) ?>1行のブロック<?php }}; block_1_line($tplValues) ?> </body> </html>
<html> <head> <!-- 変数の値を出力 --> <title>タイトル</title> </head> <body> <!-- 変数にフィルターをかけて出力 --> <こんにちは TWANGO!> <hr> <!-- コメント --> <hr> <!-- 配列のループ --> foo, bar, <hr> <!-- ループでKEYとVALUEの両方を取り出す --> 0 => foo, 1 => bar, <hr> <!-- 始まりと終わりの数を指定してループ --> 1, 2, 3, <hr> <!-- if文の条件部分はPHPそのままで --> 1 <hr> <!-- 普通のPHP文を埋め込める --> NULL <hr> <!-- 複数の行でもOK --> 今は2010-08-27 15:32:33ですよ <hr> <!-- ブロックごとに継承できる --> ここは継承 継承の入れ子 継承終わり <hr> <!-- 1行のブロックもOK --> 1行のブロック </body> </html>
<?php require('twango.php'); $values = array( 'title' => '継承', 'msg' => '継承だよ', 'kbn' => 2, 'arr' => array('foo' => 123, 'bar' => 456), ); twango('extends.html', $values);
extends.html
{% extends "tmpl.html" %} {% block test2 %} <div>{{ arr['foo'] }}</div> {% endblock test2 %} {% block 1_line %} 1行ブロックも継承 {% endblock 1_line %}
extends.html.cache.php
<?php if (!function_exists("block_test2")) {function block_test2($tplValues) { extract($tplValues) ?> <div><?php echo htmlSpecialChars($arr['foo'], ENT_QUOTES) ?></div> <?php }} if (!function_exists("block_1_line")) {function block_1_line($tplValues) { extract($tplValues) ?> 1行ブロックも継承 <?php }} twango("tmpl.html", $tplValues, $cacheDir);
<html> <head> <!-- 変数の値を出力 --> <title>継承</title> </head> <body> <!-- 変数にフィルターをかけて出力 --> 継承だよ <hr> <!-- コメント --> <hr> <!-- 配列のループ --> 123, 456, <hr> <!-- ループでKEYとVALUEの両方を取り出す --> foo => 123, bar => 456, <hr> <!-- 始まりと終わりの数を指定してループ --> 1, 2, 3, <hr> <!-- if文の条件部分はPHPそのままで --> 2 <hr> <!-- 普通のPHP文を埋め込める --> NULL <hr> <!-- 複数の行でもOK --> 今は2010-08-27 15:34:02ですよ <hr> <!-- ブロックごとに継承できる --> ここは継承 <div>123</div> 継承終わり <hr> <!-- 1行のブロックもOK --> 1行ブロックも継承 </body> </html>
Smartyは最近仕事で初めて使った。便利だと思わないので、自分から使おうとは思わない。
DjangoはGoogle App Engine SDKでHello World程度に使ったことがある。
Twigは使ったことがない。というかインストールしたことがない。
Rubyなら、というかRailsならERBを使う。そりゃそうか。
普段自分が使ってるテンプレートエンジンは、PHPです。