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を使う。
<?phpsession_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
<?phpdefine('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のテンプレートエンジン