Phelocity (Velocity風PHPテンプレートエンジン)

今まで、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&#38;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&#38;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のテンプレートエンジン

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.