ゼロと無限の間に

フリーでオープンソースなJavaScriptとかPHPとか。

ユーザ用ツール

サイト用ツール


サイドバー

何かありましたら、メールで連絡いただくか、ブログのどこかにコメント入れてくださいね ^_^

Menu

ゼロと無限の間に

はじめに

作った主なサイト

作った主な便利ツール(無料)

ログ (Blog)

php-tool-box:template-like-twig

Twig風(あるいはDjango風)のテンプレートエンジンをPHPの関数1つで作った - Twango

更新記録とコメントはTwango Archive - ゼロと無限の間のログへどうぞ。

SymfonyのテンプレートエンジンTwigやPythonのDjangoみたいなテンプレートエンジンを、PHPの関数1つで作ってみた。
create_function()を使ってるから関数2つではないかという指摘は置いておいて。)

名前は羽柴秀吉にあやかって、Twangoにした。

Twangoの使い方と特徴

TwigやDjangoと同様に、Twangoには下記のような特徴がある。

  • 変数の出力時にフィルターをかけられる
    • フィルターをかけると、変数の値を変換してから出力できる
    • Twangoは引数が1つのPHP関数なら何でもフィルターとして使える。trim()のようなPHP標準関数も使えるし、自分で作った関数でもOK
    • {{ msg|trim|strToUpper }}
  • 出力した変数がデフォルトでHTMLエスケープされる
    • Twigは普通に変数を出力するとHTMLエスケープされる。Djangoもバージョン1.0からそうなった。Twangoもこれにならった
  • テンプレートの継承ができる
    • SmartyになくTwigにある最大の機能は、テンプレートを継承できること。Twangoもテンプレートの継承ができる。(Smartyもバージョン3.0でテンプレートの継承をサポートするみたい。)
    • 具体例はこのページの下の方の例を参照

ではTwangoに無い機能はというと、コンテンツキャッシュ(テンプレートから変換したPHPのキャッシュだけでなく、DB等から取得したデータを埋め込んだHTMLのキャッシュ)機能が無い。それはテンプレートエンジンがやらなくてもいいと思うから。コンテンツキャッシュは、Apacheのmod_cacheなり、PEAR::Cache_Liteなり、P2_Cacheなりを使えばいいと思う。

Twangoの具体的な使い方についてはこのページの中ほどからの例を参考に。なるべくTwigに似せるようにしたけど面倒だったので微妙に違うところもある

ライセンス

ソースコード

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

Twangoを使う例

Twangoを呼び出すPHP

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

テンプレートを変換して作成されたPHP

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

<html>
<head>
<!-- 変数の値を出力 -->
<title>タイトル</title>
</head>
<body>
 
<!-- 変数にフィルターをかけて出力 -->
&lt;こんにちは TWANGO!&gt;
<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

<?php
require('twango.php');
 
$values = array(
	'title' => '継承',
	'msg' => '継承だよ',
	'kbn' => 2,
	'arr' => array('foo' => 123, 'bar' => 456),
);
 
twango('extends.html', $values);

「Twangoを使う例」のテンプレートを継承するテンプレート

extends.html

{% extends "tmpl.html" %}
 
{% block test2 %}
	<div>{{ arr['foo'] }}</div>
{% endblock test2 %}
 
{% block 1_line %} 1行ブロックも継承  {% endblock 1_line %}

継承用テンプレートを変換して生成されたPHP

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

<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です。

php-tool-box/template-like-twig.txt · 最終更新: 2010/09/06 22:29 by dgbadmin