Smarty実践入門

Smartyの初心者用サイトやリファレンスを見たけど利点が良くわからない人向け。
実際に仕事でありそうなシチュを使って解説していきます。

データベースの値を表示してみる

まずは商品テーブルの値をズラズラと表示してみましょう。

■テーブルを用意します。
ありがちな商品テーブル「product」が以下のあるとします。
テーブル名:product

id name comment price ins_time
1 りんご 青森産の美味しいリンゴ!ビタミンもあるよ。 1000 2009/12/12 23:20:00
2 みかん 愛媛産のおいしいミカン 500 2010/02/11 10:00:00

は改行コードとして置き換えて見てください。


PHPファイルを用意する
ロジック側のファイルを用意します。
Smartyの初期設定が完了しており、PDOが使える状態とします。
$smartySmartyクラスのインスタンス
$pdo … PDOクラスのインスタンス

$stmt = $pdo->query("select * from product order by id");  //productテーブルからデータ取得
$smarty->assign("p_data", $stmt->fetchAll());                        //Smarty変数に格納する             
$smarty->display("product.html");                          //product.htmlというテンプレートファイルを表示

実際にはtry{}catch{}で例外処理などがありますが、ここでは入れてません。
では、ロジック側のPHPファイルはこれ以上触らずにSmartyだけであとは色々やっていきましょう。


■テンプレートファイルを用意します。
ファイル名:product.html

<table border>
<tr bgcolor="#c0c0ff">
	<th>id</th>
	<th>name</th>
	<th>comment</th>
	<th>price</th>
	<th>ins_date</th>
</tr>
{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{$val.comment}</td>
	<td>{$val.price}</td>
	<td>{$val.ins_date}</td>
</tr>
{/foreach}

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!ビタミンもあるよ。 1000 2009-12-12 23:20:00+09
2 みかん 愛媛産のおいしいミカン 500 2010-02-11 10:00:00+09

データベースのクエリー結果をそのまま出したような感じですね。
foreachで$p_data配列を回して、連想配列となった$valを表示しているだけです。
ここからはproduct.htmlだけをメインに触っていきます。

ちなみに、結果が0件だった場合に「該当結果が見つかりませんでした」というメッセージを
出したいときがありますよね。そういう時は以下のような感じに。

<table border>
<tr bgcolor="#c0c0ff">
	<th>id</th>
	<th>name</th>
	<th>comment</th>
	<th>price</th>
	<th>ins_date</th>
</tr>
{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{$val.comment}</td>
	<td>{$val.price}</td>
	<td>{$val.ins_date}</td>
</tr>
{foreachelse}
<tr>
	<td colspan=5>該当結果が見つかりませんでした</td>
</tr>
{/foreach}

上記のように{foreachelse}を追加してその下に内容を書きます。
今回は実際にデータがあるというのを前提にしてますので今後は省略します。

日付フォーマットを変える

日付の部分がちょっとカッコ悪いので直しましょう。

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{$val.comment}</td>
	<td>{$val.price}</td>
	<td>{$val.ins_date|date_format:"%Y/%m/%d"}</td>
</tr>
{/foreach}

$val.ins_dateの後ろに修飾子のdate_formatをつけてます。引数に"%Y/%m/%d"を与えてます。

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!ビタミンもあるよ。 1000 2009/12/12
2 みかん 愛媛産のおいしいミカン 500 2010/02/11
日付が綺麗になりました。

改行する

よく見ると「青森産の美味しいリンゴ!ビタミンもあるよ。」が改行されていませんので改行させましょう。

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{$val.comment|nl2br}</td>
	<td>{$val.price}</td>
	<td>{$val.ins_date|date_format:"%Y/%m/%d"}</td>
</tr>
{/foreach}

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!

ビタミンもあるよ。
1000 2009/12/12
2 みかん 愛媛産のおいしいミカン 500 2010/02/11

数値をカンマ区切りにする

リンゴのpriceが1000なのにカンマ区切りされていません。
カンマ区切りにしましょう

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{$val.comment|nl2br}</td>
	<td>{$val.price|number_format}</td>
	<td>{$val.ins_date|date_format:"%Y/%m/%d"}</td>
</tr>
{/foreach}

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!

ビタミンもあるよ。
1,000 2009/12/12
2 みかん 愛媛産のおいしいミカン 500 2010/02/11

奇数行と偶数行は違う色にする

よくあるデザインの一つのテーブルの奇数行と偶数行の色を変えてみます。

{foreach from=$p_data item="val"}
<tr bgcolor="{cycle values="#FFE8AE,#FFFFFF"}">
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{$val.comment|nl2br}</td>
	<td>{$val.price|number_format}</td>
	<td>{$val.ins_date|date_format:"%Y/%m/%d"}</td>
</tr>
{/foreach}

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!

ビタミンもあるよ。
1,000 2009/12/12
2 みかん 愛媛産のおいしいミカン 500 2010/02/11
データが2つしかないのでわかりづらいかもしれませんが、3つ目以降も色が変わっていきます。

配列の数を表示する

今回は検索結果の数が少ないので配列の数を表示して件数を出してみます。
速度重視ならばデータとは別途count(*)のSQLを実行して格納するのが良いと思います。

該当数:{$p_data|@count}件

■結果
該当数:2件

2件と表示されました。@はエラー制御文字ですがこれがないとSmartyではcountが動作しません。

文字列を変換する

りんごは「美味しい」なのに、みかんは「おいしい」と統一感がないので、「美味しい」に統一します

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{"おいしい"|str_replace:"美味しい":$val.comment|nl2br}</td>
	<td>{$val.price|number_format}</td>
	<td>{$val.ins_date|date_format:"%Y/%m/%d"}</td>
</tr>
{/foreach}

トリッキーな書き方なので少し説明しますと、
「文字列|関数」という書き方は、これは文字列が第1引数になり、関数を実行していることになります。
引数を増やす時は「文字列|関数:第2引数:第3引数…」とコロンで区切っていきます。
ただ、str_replace関数は「str_replace(変換前文字列, 変換後文字列, 変換対象文字列)」という引数の
受け方なので、上のようなトリッキーな書き方になってしまうのです。
極力使わないほうがソースが見やすいので、どうしようもない時に利用してください。

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!

ビタミンもあるよ。
1,000 2009/12/12
2 みかん 愛媛産の美味しいミカン 500 2010/02/11
テンプレート側とPHP側の文字コードが違う場合は正常に変換されませんのでご注意ください。

プラグインを作ってみる。

Smartyでは自作の修飾子/関数を作成出来ます。
今まで使ってきたnl2brやdate_formatもデフォルトでインストールされているプラグインで実装されてます。
例でいくつか作ってみましょう。

カタカナを半角にするプラグインを作成する

カタカナを半角に変換するプラグインを作成してみましょう。
まずインストールしているSmartyディレクトリの中にある「plugins」ディレクトリの中を見てみてください。
function.****やmodifier.*****というものがあると思います。
functionから始まるものが関数で、modifierから始まるものが修飾子になります。

この中で「modifier.cat.php」というものがありますので、これをコピーして「modifier.hankana.php」を作成してください。
ファイルを開くと

function smarty_modifier_cat($string, $cat)
{
    return $string . $cat;
}

とあると思います。
Smartyプラグインと認識するには、まずファイル名の「modifier.******.php」の****の部分と、
この関数名の「function smarty_modifier_******」が同じでなければいけません。

ここでは半角カタカナに変換するプラグインの名前を「hankana」にして作ってみます。
ですので、下記のように関数名を変更してください。
あと念のためキャラセットも受け取れるようにしておきましょう

function smarty_modifier_hankana($string, $charaset="UTF-8")
{
    return $string . $cat;
}

では実際に処理の中身を書いていきましょう。
といっても一行で済みますね。

function smarty_modifier_hankana($string, $charaset="UTF-8")
{
    return mb_convert_kana($string,"k",$charaset);
}

引数のルールは前述したように、第1引数に対象の文字列がきます。
出来たら保存して実際に使ってみましょう!
product.htmlに戻って編集です。

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{"おいしい"|str_replace:"美味しい":$val.comment|nl2br|hankana}</td>
	<td>{$val.price|number_format}</td>
	<td>{$val.ins_date|date_format:"%Y/%m/%d"}</td>
</tr>
{/foreachelse}

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!

ビタミンもあるよ。
1,000 2009/12/12
2 みかん 愛媛産の美味しいミカン 500 2010/02/11
ちゃんと半角カナになってますね。

日付を「平成」などの元号表記にする

元号表記は結構要望があったりするのでプラグインを作っておきましょう。
「modifier.ymd2gengo.php」というファイルをpluginsの中に作成してください。
実際の処理は以下のような形です。

function smarty_modifier_ymd2gengo($string,$format='%G%Y年%m月%d日')
{
	if( !preg_match('/^([0-9]{4})[\/\-]([0-9]{1,2})[\/\-]([0-9]{1,2}).*$/',trim($string),$regs) ){
		return $string;
	}
	list($tmp,$year,$mon,$day) = $regs;
	list($gengo,$g_year) = GetGengo( $year );
	
	$arrConvList = array( '%G' => $gengo,
			      '%Y' => $g_year,
			      '%m' => $mon,
			      '%d' => $day
			     );
	$string = $format;
	foreach( $arrConvList as $conv_key => $conv_val ){
		$string = str_replace($conv_key,$conv_val,$string);
	}
	
	return $string;
}

if( !function_exists("GetGengo") ){
	function GetGengo( $year ){
		$gengo = "";
		if( $year >= 1867 && $year < 1912 ){
			//明治判定
			$gengo = array("明治",$year - 1967);
		}
		else if( $year >= 1912 && $year < 1926 ){
			//大正判定
			$gengo = array("大正",$year - 1911);
		}
		else if( $year >= 1926 && $year < 1989 ){
			//昭和判定
			$gengo = array("昭和",$year - 1925);
		}
		else if( $year >= 1989 ){
			//平成判定
			$gengo = array("平成",$year - 1988);
		}
		
		return $gengo;
		
	 }
}

プラグインが出来たらproduct.htmlに実装します。

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{"おいしい"|str_replace:"美味しい":$val.comment|nl2br|hankana}</td>
	<td>{$val.price|number_format}</td>
	<td>{$val.ins_date|ymd2gengo}</td>
</tr>	
{/foreach}

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!

ビタミンもあるよ。
1,000 平成21年12月12日
2 みかん 愛媛産の美味しいミカン 500 平成22年02月11日

プラグインの第2引数にフォーマットを指定出来るようにしていますので、
元号年と月だけ表示したい!というときは以下のような感じになります。

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id}</td>
	<td>{$val.name}</td>
	<td>{"おいしい"|str_replace:"美味しい":$val.comment|nl2br|hankana}</td>
	<td>{$val.price|number_format}</td>
	<td>{$val.ins_date|ymd2gengo:"%G%Y年%m月"}</td>
</tr>
{/foreach}

■結果

id name comment price ins_date
1 りんご 青森産の美味しいリンゴ!

ビタミンもあるよ。
1,000 平成21年12月
2 みかん 愛媛産の美味しいミカン 500 平成22年02月
月まで表示されました。元号だけ出したいときなども利用出来ます。

最後にエスケープ

最後になりましたが、HTMLタグ等をエスケープする修飾子があります。
セキュリティ的にも基本出力するものに対してはつけていったほうが良いです。

{foreach from=$p_data item="val"}
<tr>
	<td>{$val.id|escape}</td>
	<td>{$val.name|escape}</td>
	<td>{"おいしい"|str_replace:"美味しい":$val.comment|hankana|escape|nl2br}</td>
	<td>{$val.price|number_format|escape}</td>
	<td>{$val.ins_date|ymd2gengo:"%G%Y年%m月"|escape}</td>
</tr>
{/foreach}

といった感じで、「escape」修飾子をつけるとエスケープされます。
nl2brの後にescapeするとbrタグがそのまま出力されたりするので注意してください。

終わり

まだまだ紹介したい機能がありますが、要望などがあれば第2段も書いていこうかなと思います。
次書くとしたらfilter等のテンプレート以外の機能もピックアップする予定です。