Phelocity (Velocity風PHPテンプレートエンジン)
2009/06/23
今まで、PHPでそれ程沢山のページがあるシステムを作ったことが無かったので、PHPのテンプレートエンジンは、不要だと思ってた。PHP自体がテンプレートエンジンなんだから、別の文法とか導入するのは、あまり意味のあることだとは思えなかった。
でも、一人で結構ページ数の多いシステムをPHPで作ってると、さすがに面倒になってくる。何が面倒って、「<?php」のタグを閉じるのが面倒。「<?php」 を書くのも面倒なんだけど。「Velocity *1 だったらな」と思う気持が抑えられなくなってきた。
Velocityのいいところ
Velocityでは、#の後から改行までに制御構文を書ける。改行までってのがポイントで、閉じなくていいってことになる。
そこで、phpでVelocity風のテンプレートエンジンPhelocityを書いてみた。(なんか、1年半前くらいの5番煎じくらいだけど)
仕様
仕様は小さい方がいいので、下のものだけ。(あんまり厳密にVelocityを再現しようとすると手間がかかるし、無駄な記述も増えてしまうので)
- 「# 」(#のあとに半角スペース)があったら、行末までをphpスクリプトと解釈する。
- 「#{…..}」があったら、…..の部分をphpスクリプトと解釈する。
- 「#h{…..}」があったら、…..の部分をphpスクリプトとして解釈した結果をHTMLエスケープして出力する。
- テンプレートに記述された変数の解決は、Phelocityに渡されたコンテキスト、$_REQUEST、$_SESSION、$_SERVER、$_COOKIEの順で検索される。
使い方
index.php
リクエストされるファイル。Phelocityを使う。
<?php session_start(); require_once('Phelocity.php'); $context = array( 'title'=>'Example', 'list'=>array(10, '<A&B>', NULL)); $_SESSION['greeting'] = 'helo'; $phelocity = new Phelocity($context); $phelocity->merge('template.html'); ?>
template.html
<html> <head> <title>Pelocity</title> </head> <body> <h1>#h{$title}</h1> <ul> # foreach ($list as $e) { <li>#h{$e}</li> # } </ul> #h{$greeting} </body> </html>
Phelocity本体
Phelocity.php
<?php define('PHELOCITY_TEMPLATE_DIR', 'tmp'); /** * Velocity風テンプレートエンジン * * 使い方 * - 使用する側 * require_once('Phelocity.php'); * $context = array('title'=>'Example', * 'list'=>array(10, '<A&B>', NULL)); * $phelocity = new Phelocity($context); * $phelocity->merge('template.html'); * - テンプレート * <h1>#h{$title}</h1> * <ul> * # foreach ($list as $e) { * #h{$e} * # } * </ul> * @author smeghead */ class Phelocity { private $context = null; /** * コンストラクタ * @param 連想配列 $values 展開する値の連想配列 */ function __construct($values = array()) { $this->context = (array)$values; } /** * 値を設定する。 * @param string $key キー * @param object $value 値 */ function set($key, $value) { $this->context[$key] = $value; } /** * 特殊文法を変換し、実行する。 * @param string $template_filename テンプレートファイル名 */ function merge($template_filename) { if (!file_exists(PHELOCITY_TEMPLATE_DIR)) { die('ERROR: not exists temporaty directory.'); } $temporary_filename = PHELOCITY_TEMPLATE_DIR . "/$template_filename"; if (!file_exists($temporary_filename) || filemtime($template_filename) > filemtime($temporary_filename)) { //テンプレートが更新されている場合のみコンパイル作業を行なう。 $this->compile($template_filename, $temporary_filename); } //変数の展開 EXTR_SKIPで上書きしないようにしているので、最初の方が優先順位が高い。 extract($this->context, EXTR_SKIP); extract($_REQUEST, EXTR_SKIP); if (isset($GLOBALS['_SESSION'])) { extract($_SESSION, EXTR_SKIP); } extract($_SERVER, EXTR_SKIP); extract($_COOKIE, EXTR_SKIP); if (preg_match('/[:\0]/', $temporary_filename)) { //不正なファイルを開かないようにするため。 die('invalid filename.' . $temporary_filename); } //Phelocityの処理時には、Undefined Indexの警告は出て欲しくない。 $warning_level = error_reporting(E_ERROR | E_WARNING | E_PARSE); require($temporary_filename); error_reporting($warning_level); } /** * テンプレートファイル名を解析して、純粋なphpに変換し一時ファイルに保存する。 * @param string $template_filename テンプレートファイル名 * @param string $temporary_filename 一時ファイル名 */ function compile($template_filename, $temporary_filename) { $template_str = file_get_contents($template_filename); $template_str = preg_replace('/# (.*?)[\r\n]/', '<?php $1 ?>' . PHP_EOL, $template_str); $template_str = preg_replace('/#{([^}]*)}/', '<?php $1 ?>', $template_str); $template_str = preg_replace('/#h{([^}]*)}/', '<?php print htmlspecialchars($1, ENT_QUOTES) ?>', $template_str); $pathinfos = pathinfo($temporary_filename); @mkdir($pathinfos['dirname']); // 既に存在している場合は、警告を無視する。 file_put_contents($temporary_filename, $template_str, LOCK_EX) or die('failed to open file. ' . $temporary_filename); } } ?>
デメリット
- #に副作用が出るのを気にする必要がある。
- NetBeansのサポートが受けられなくなる。
Phelocityって言ってみたかっただけ。
*1:javaのテンプレートエンジン