2006-06-12

はじめに

本サイト目的は開発環境の構築です。Smartyへの深入りは本サイト(Ground-Sunlight)の目的にマッチしていません。「Smartyを理解する」のはどちらかと言うと「Water-Sunlight」の話題です。でもあえてここでSmartyを話題にするのには理由があります。開発環境とは物的なものだけではありません。人的そしてグループウェア的要素も不可欠です。前章「Smatryを利用する」でも説明したように「プレゼンテーション層とアプリケーション層の分離」はWebプログラミングにとって必要不可欠な要素です。Webプログラミングのプレゼンテーション層は、業務ソフトのそれとは質が違います。操作性以外のデザイン性という品質特性がそこにはあります。この要素に通常のプログラマは立ち入る事ができません。PHPを使う上で「プレゼンテーション層とアプリケーション層の分離」という開発環境の問題を解決するのには、Smartyが必要なのです。

Smartyを理解する

多くのSmartyの解説書では、プラグインは拡張機能として最後の方で紹介されています。 これについて、私は不満があります。私はSmartyの勉強をしていく過程で、 プラグインを先に勉強することがSmartyを理解する近道であると気が付きました。

ここでは、プラグインを通してSmartyを理解する事を試みます。Smartyの詳細な使い方に立ち入りません。 Smartyの使い方については、以下のマニュアルを参照して下さい。

※和訳マニュアルはほんの少しバージョンが古いですが、十分使えます。(感謝!感謝!)

Smartyのプラグイン

Smartyのプラグインも他のそれと同じように既往のソフトに追加機能を提供するためのプログラムの事です。 Smartyの強力なプラグインは利用者にとって非常に魅力的であり、また開発者にとっては大変勉強になります。 実はテンプレートエンジンにSmartyを使う理由はここにあるのかもしれません。

Smartyにはいろいろな種類のプラグインが用意されいます。 これらのプラグインを利用すればSmartyの多くの機能はカスタマイズが可能になります。 この事は同時にプラグインを理解すればSmartyが理解できる事の根拠とります。

Smartyには以下のプラグインが用意されています。

  1. プログラマのためのプラグイン
    • リソース プラグイン
    • プリフィルタ プラグイン
    • ポストフィルタ プラグイン
    • 出力フィルタ プラグイン
  2. デザイナのためのプラグイン
    • 修正子 プラグイン
    • テンプレート関数 プラグイン
    • ブロック関数 プラグイン
    • insert プラグイン
    • コンパイラ関数 プラグイン

上では「デザイナのためのプラグイン」となっていますが、プラグインを作るのはプログラマです。テンプレートのデザイナはそれを使うだけです。「プログラマのためのプラグイン」はデザイナの目に触れる事はありません。

コンパイル処理と実行処理

Smatryを理解するコツは「コンパイル処理」と「実行処理」の違いを正確に把握する事です。簡単に言うと以下のようになります。

  • 「コンパイル処理」とは {Smartyタグ} を <?php コード ?> に変換する処理の事です。
  • 「実行処理」とは <?php コード ?> を htmlコード に変換する処理の事です。

実例

テンプレート: hello.html

<html>
<head><title>Hello World</title></head>
<body>
Hello, {$name}!
</body>
</html>

コンパイル処理後:

<?php /* Smarty version 2.6.14, created on 2006-06-15 17:46:52
         compiled from hello.html */ ?>
<html>
<head><title>Hello World</title></head>
<body>
Hello, <?php echo $this->_tpl_vars['name']; ?>
!
</body>
</html>

実行処理後:キャシュなしの場合

<head><title>Hello World</title></head>
<body>
Hello, Smarty Jones!
</body>
</html>

Smartyのキャシュ機能のが有効な場合は、htmlコードの前にキャシュ有効期間などの情報が出力されます。

実行処理後:キャシュありの場合

132
a:4:{s:8:"template";a:1:"expires";i:1150365103;s:13: ・・・ }<html>
<head><title>Hello World</title></head>
<body>
Hello, Smarty Jones!
</body>
</html>

プラグインの概要

本編では、Smartyのプラグインを以下のように分類します。

  • 関数系プラグイン --- テンプレートで使える新しい関数を提供します
    • コンパイラ関数
    • 修正子
    • テンプレート関数
    • ブロック関数
    • insert
  • フィルタ系プラグイン --- コンパイル処理と実行処理のカスタマイズを提供します
    • プリフィルタ
    • ポストフィルタ
    • 出力フィルタ
  • リソースプラグイン --- テンプレートリソースを管理する一連のハンドラ関数を提供します

下図は、関数系プラグインとフィルタ系プラグインの役割を大まかに説明したものです。

plugin01.gif

関数系プラグイン

  • まずは、includeですがこれはプラグインと関係ありません(プラグインとの対比のために図示しています)。includeは他のテンプレートを取り込む関数です。includeされるテンプレートは呼出し側とは別にコンパイルされ、実際のincludeは実行処理で行われます。
  • includeはテンプレートを取り込むのに使用しますが、コンパイラ関数はphpコードを取り込む場合に使います。コンパイラ関数はコンパイル時に呼び出され、phpコードをテンプレート内に展開します。取り込んだphpコードの実行は実行処理で行われます。

    php関数を使えはphpコードを直接テンプレートに記述できますが、この行為は「プレゼンテーションとアプリケーションの分離の原則」から外れています。phpコードはコンパイラ関数経由でテンプレートに取り込むべきです。
  • 修正子とはテンプレート変数を修飾するためのフィルタです。テンプレート変数の後の|(パイプ)に続けて修飾子を書きます。Smatryに標準で付属している修正子もプラグインとして提供されています。(SMARTY_DIR/pluginsの下にあります)
    修正子はテンプレート変数と同じく実行処理で評価されます。
  • テンプレート関数は{func}形式の関数で、ブロック関数は {func}・・・{/func} 形式の関数です。両者をSmartyでは「カスタム関数」と呼んでいます。カスタム関数とは「組み込み関数」に対する用語で、プラグインで変更可能な関数の事です。一方、組み込み関数はプラグインによる変更はできません。

    組み込み関数には if, foreach, include, insert, php などの基本的な関数が含まれます。カスタム関数にはSmatryに標準で付属しているものがあります。(SMARTY_DIR/pluginsの下にあります)
    カスタム関数や組み込み関数もテンプレート変数や修正子と同様に実行処理で評価されます。
  • insertはインサートプラグインを呼び出す関数です。insertはincludeやコンパイラ関数に似ていまが、インサートプラグインの出力はキャッシュされません

フイルタ系プラグイン

関数系プラグインはそれを呼び出しているテンプレートのみに影響がありますが、フィルタ系プラグインは全てのテンプレートに影響を及ぼす事ができます。但し、無条件に全てのテンプレートに影響がある訳ではなく、選択的に作用させる事ができます。フイルタを適用するは、autoload_filtersメソッド などによってフイルタをロードしてく必要があります。

上図から分かるように、それぞれのフイルタはコンパイル処理の前後と実行処理の後に適用されます。

リソースプラグイン

リソースプラグインは、テンプレートなどのリソースを提供する一般的な方法を与えてくれます。例えば、リソースプラグインを使えば、テンプレートをファイル管理ではなくデータベース管理にすることができます。本編ではリソースプラグインに深入りしませんが、これは興味深いトピックです。xoopsもSmartyを使っており、テンプレートはリソースプラグインを使ったデータベース管理になっています。興味ある方は、xoopsのソースコードを参照して下さい。

プラグインの作り方

プラグインを作る準備

プラグインを作る場合は、専用のSmartyクラス(MySmarty)のコンストラクタを少し変更する必要があります。

$this->force_compile = true;

Smartyはテンプレートが変更されないとコンパイルしてくれません。プラグインの開発中は、$force_compileをtrueに設定し、テンプレートが呼び出される毎に強制的にコンパイルする必要があります。リリース時はfalseに戻しておきましょう。

array_unshift($this->plugins_dir, "$mydir/plugins/");

$plugins_dir はプラグインを設置するディレクトリです。配列で指定します。上の例では、デフォルトの$plugins_dir(SMARTY_DIR下のplugins)に自前のプラグインを設置するディレクトリを追加しています。

Smartyは$plugins_dirからプラグインを探します。そして、最終的にはphpのインクルードパス(include_path)を探しますが、明示的に $plugins_dir を設定した方が良いパフォーマンスが得られます。$plugins_dirに設置した関数系プラグインは、テンプレートで呼び出された時に自動で暗黙的にロードされます。しかし、フィルタ系プラグインは、手動で明示的にロードする必要があります(以下の「フィルタのロード」を参照して下さい)。

プラグインをロードする別の方法は、register_function(テンプレート関数)やrgister_block(ブロック関数)などを使う方法です。この方法を使えば、手動で動的にプラグインをロードすることができますが、本編では使用しません。

プラグインの命名規則

プラグインのファイル名
type.name.php
プラグインの関数名
smarty_type_name()

nameは任意の文字列を指定できますが、英数字とアンダースコアのみ使用できます。 typeはプラグインのタイプを表し、次のうちのいずれがになります。

typeプラグインの種類
compilerコンパイラ関数
modifier修正子
functionテンプレート関数
blockブロッグ関数
insertinsertプラグイン
prefilterプレ フィルタ
postfilterポスト フィルタ
outputfilter出力 フィルタ
resourceリソースプラグイン

例:コンパイラ関数 --- compiler.date.php と smarty_compiler_date()
例:修正子 --- modifier.data_format.php と smarty_modifier_data_format()

フィルタのロード

フィルタは関数系プラグインと異なり $plugins_dir にフィルタファイルを設置しただけでは有効になりません。$plugins_dir に設置したフィルタを有効にする最も簡単な方法は、$autoload_filters メンバ変数を使うことです。

$this->autoload_filters = array('pre' => array('pre_filer1', 'pre_filer2),
                                'post' => array('post_filer1'));

上の例では、2つのプレフィルタと1つのポストフィルタをロードにしています。この連想配列には、キーとしてフィルタの種類を、値としてフィルタの名前を設定します(複数指定可)。フィルタの種類には、pre(プレフィルタ)、post(ポストフィルタ)、output(出力フィルタ)のいずれかを指定します。

プラグインの実装

以下に比較的よく使うプラグインの関数仕様と実装例を説明します。その他のプラグインについてはSmatryのマニュアルを参照して下さい。

コンパイラ関数

mixed smarty_compiler_name(string $tag_arg, object &$smarty)

  • $tag_arg: 関数名に続く全ての引数が1つの文字列として渡されます。引数の解析は関数内で行う必要があります。
  • $smarty : Smartyオブジェクト。
  • 戻り値 : テンプレートに挿入されるphpコードを文字列として返します。

例:コンパイラ関数 プラグイン

テンプレートがコンパイル処理された日時と実行処理された日時を出力するプラグインを作ります。

compiler.cr_time.php

<?php
function smarty_compiler_cr_time($tag_arg, &$smarty)
{
  $format='Y-m-d H:i:s';
  if ($tag_arg)
  {
    list($name,$value) = explode("=",$tag_arg);
    if (strtolower($name)=='format') $format=trim($value,"\"'");
  }
  return "echo 'compiled: ".date($format).", Run: '.date('$format')";
}
?>

プラグインの使用例

<?php
require_once('MySmarty.class.php');
$objSmarty =& new MySmarty;

$objSmarty->assign('name','Smarty Jones');
$objSmarty->display('plugin_compiler.html');
?>

plugin_compiler.html

<html>
<head><title>Hello World</title></head>
<body>
Hello, {$name}!<br />
{cr_time format='y/m/d H:i:s'}
</body>
</html>

コンパイル後のPHPコードを見るとコンパイラ関数の挙動が良く分かります。

<?php /* Smarty version 2.6.14, created on 2006-06-16 15:53:34
         compiled from plugin_compiler.html */ ?>
<html>
<head><title>Hello World</title></head>
<body>
Hello, <?php echo $this->_tpl_vars['name']; ?>
!<br />
<?php echo 'compiled: 06/06/16 15:53:34, Run: '.date('y/m/d H:i:s') ?>
</body>
</html>

修正子

mixed smarty_modifier_name (mixed $value, [mixed $param1, ...])

  • $value : 通常は修正子の前のテンプレート変数が渡されます。
  • $param1: 修正子の属性が渡されます。修正子の属性は:で区切って渡します。(下の例を参照)
  • 戻り値 : 処理後の値を文字列として返します。

例:修正子 プラグイン

マルチバイト対応した簡単なトランケート修正子を作ります。Smartyに付属しているtruncate修正子は日本語に対応していないので文字化けします。

modifier.jp_truncate.php

<?php
function smarty_modifier_jp_truncate($string, $length = 80, $etc = '...')
{
  if (mb_strlen($string) > $length) {
    return mb_substr($string, 0, $length-mb_strlen($etc)).$etc;
  } else {
    return $string;
  }
}
?>

プラグインの使用例

<?php
require_once('MySmarty.class.php');
$objSmarty =& new MySmarty;

$objSmarty->assign('name','スマーティジョーンズ');
$objSmarty->display('plugin_modifier.html');
?>

plugin_modifier.html

<html>
<head><title>Hello World</title></head>
<body>
Hello, {$name|truncate:8:'...'}!<br />  {* 文字化けします *}
Hello, {$name|jp_truncate:8:'...'}!<br /> {* 正常に表示できます *}
</body>
</html>

テンプレート関数

mixed smarty_function_name (array $params, object &$smarty)

  • $params: 関数名に続く属性が連想配列として渡されます。連想配列のキーは属性名です。
  • $smarty: Smartyオブジェクト。
  • 戻り値 : 通常、テンプレートに挿入される文字列を返します。

例:テンプレート関数 プラグイン

良く使うHTML要素はテンプレート関数として実装しておくと便利です。日付(html_select_date)や時間(html_select_time)の選択はSmartyに標準で付属しています。ここでは都道府県のセレクトボックスを作って見ます。この時、html_options関数を利用すると便利です。html_options関数は通常、テンプレートから呼び出されますが、ここではテンプレート関数の中から呼び出します。

function.html_select_ken.php

<?php
function smarty_function_html_select_ken($params, &$smarty)
{
  static $ken = array(1=>
  '北海道', '青森県',  '岩手県',  '宮城県',  '秋田県',  '山形県',
  '福島県', '茨城県',  '栃木県',  '群馬県',  '埼玉県',  '千葉県',
  '東京都', '神奈川県','新潟県',  '富山県',  '石川県',  '福井県',
  '山梨県', '長野県',  '岐阜県',  '静岡県',  '愛知県',  '三重県',
  '滋賀県', '京都府',  '大阪府',  '兵庫県',  '奈良県',  '和歌山県',
  '鳥取県', '島根県',  '岡山県',  '広島県',  '山口県',  '徳島県',
  '香川県', '愛媛県',  '高知県',  '福岡県',  '佐賀県',  '長崎県',
  '熊本県', '大分県',  '宮崎県',  '鹿児島県','沖縄県'); 

  require_once $smarty->_get_plugin_filepath('function','html_options');

  $params['options'] = $ken;
  return smarty_function_html_options($params, $smarty);
}
?>

※_get_plugin_filepath はプラグインファイルを検索する非公開のメソッドです。

プラグインの使用例

<?php
require_once('MySmarty.class.php');
$objSmarty =& new MySmarty;

$objSmarty->assign('name','Smarty Jones');
$objSmarty->assign('selected_ken',28);
$objSmarty->display('plugin_function.html');
?>

plugin_function.html

<html>
<head><title>Hello World</title></head>
<body>
Hello, {$name}!<br />
Where are you from:
{html_select_ken name='ken' selected=$selected_ken}
?
</body>
</html>

insert プラグイン

string smarty_insert_name (array $params, object &$smarty)

  • $params: 関数名に続く属性が連想配列として渡されます。連想配列のキーは属性名です。
  • $smarty: Smartyオブジェクト。
  • 戻り値 : 通常、テンプレートに挿入される文字列を返します。

insertプラグインの動作や実装はテンプレート関数とほぼ同じです。但し、キャッシュが有効な場合でも、insertプラグインの出力はキャッシュされません。これは以下のような場合に利用でいます。

  • バナー広告などのランダム表示
  • 検索結果などのインタラクティブな表示
  • 天気予報や渋滞情報なでのタイムリーな表示

例:insert プラグイン

バナー表示の例を以下に示します。この例ではサイト内のバナー表示を一手に引き受けるコードをinsertプラグインを使って実装しています。

insert.banner.php

<?php
function smarty_insert_banner($params, &$smarty)
{
  //
  // 必須パラメータをチャックします
  // エラーの場合は trigger_error() を使ってメッセージを出力します。
  //
  if (empty($params['id'])) {
    $smarty->trigger_error("insert banner: missing 'id' parameter");
    return;
  }
  //
  // バナーID($params['id'])) から広告スペースを取得し、
  // そこに表示する イメージ($image) を所定の条件で決めるます。
  // 
  // 例:
  $image = "{$params['id']}-" . rand(1,5) . ".gif";
  return "<img src='$image' />";
}
?>

プラグインの使用例

<?php
require_once('MySmarty.class.php');
$objSmarty =& new MySmarty;

$objSmarty->assign('banner_id',1);
$objSmarty->assign('name','Smarty Jones');
$objSmarty->display('plugin_insert.html');
?>

plugin_insert.html

<html>
<head><title>Hello World</title></head>
<body>
<div class="banner">
{insert name="banner" id=$banner_id }
</div>

Hello, {$name}!
</body>
</html>

フィルタ系プラグイン

プレフィルタ :string smarty_prefilter_name (string $source, object &$smarty)
ポストフィルタ:string smarty_postfilter_name (string $compiled, object &$smarty)
出力フィルタ :string smarty_outputfilter_name (string $template_output, object &$smarty)

  • $source: コンパイルする直前のテンプレートソースが渡されます。
  • $compiled: コンパイルした直後のphpコードが渡されます。
  • $template_output: 実行処理後のhtmlコードが渡されます。
  • $smarty: Smartyオブジェクト。
  • 戻り値 : 変換後のテキスト(テンプレートソース/phpコード/htmlコード)を返します。

全てのフィルタ系プラグインのI/Oは変換元のテキストを入力し、変換後のテキストを出力する形式になっています。従って、コードの実装方法はおのずから正規表現を用いた変換関数を使ったものになります。また、フィルタ本来の利用ではなく、フィルタ処理のタイミングを利用したロギング処理などに利用することもできます。

例:出力フィルタ プラグイン

システムの機能拡張を行う場合、テンプレートを変更せずにHTML内に何かを挿入したい場合があります。下の例では、HTMLのHEAD部の中にCSSファイルを呼び出すLINKタグを挿入しています。

<?php
function smarty_outputfilter_insert_css($output, &$smarty)
{
  $css = '<link rel="stylesheet" type="text/css" href="sample.css" />';
  return preg_replace('/(<\/head>)/i',"\n$css$1", $output);
}
?>

プラグインの使用例

<?php
require_once('MySmarty.class.php');
$objSmarty =& new MySmarty;
$objSmarty->autoload_filters = array('output' => array('insert_css'));

$objSmarty->assign('name','Smarty Jones');
$objSmarty->display('plugin_outputfilter.html');
?>

plugin_outputfilter.html

<html>
<head><title>Hello World</title></head>
<body>
Hello, {$name}!
</body>
</html>

上例では、フィルタ系プラグインをロードするのに $autoload_filters メンバ変数を使っています。



最終更新のRSS Last-modified: Thu, 29 Jun 2006 16:37:53 JST (3951d)