ようこそゲストさん

mitc - 日記

2007/05/13(日) Smartyの自分メモを書いてみた

はてブ 2007/05/13 26:42 PHPmiff
Smartyを使う必要が出てきたので,最低限の機能が使えるように調べた情報をメモ.
情報は大体マニュアルの和訳版から取ってきてます.
書いた時点で対象としたSmartyのバージョンは2.6.18.

Ver2-6-7 マニュアル*1
追記:後で見たら公式に日本語マニュアルがあった……とほほ.
追記(2007/05/28):条件分岐の所でandとorを逆に書いていたので修正
追記2008/04/19:数式記法とincludeを追加

目次

*1 : 2007/5/13時点でSmartyの最新版は2.6.18なんだけど2-6-7というのは何を意味してるのか不明.Ver.2.6マニュアルの第7版?

テンプレートを読み込んで表示するだけ

example.php
<?
// include_pathをSmartyインストールディレクトリに通しておく必要がある
include("Smarty.class.php");

// Smartyは,生成したオブジェクトを通して操作する
$objSmarty = new Smarty();
// テンプレートディレクトリとキャッシュディレクトリは,デフォルトでこの値になってる.
$objSmarty->template_dir = "./templates";
$objSmarty->compile_dir  = "./templates_c";

// assign(テンプレート内の変数名, 割り当てる値); でテンプレート内の変数に値を割り当てる
$objSmarty->assign('body_message', 'テスト');

// 当然ながら変数も使える
$bodyMessage = "変数テスト";
$objSmarty->assign('var_body_message', $bodyMessage);

// 表示する
$objSmarty->display("example.tpl");

// fetchとは,「取ってくる」という意味.
// 棒を投げて犬に取ってこさせるような時に言うみたい// つまりは,処理結果を取ってくる.そんなイメージ.
// テンプレート内の変数にassignしたデータを割り当てた文字列,
// つまりdisplayした時に表示されるHTMLデータを取得する.
$htmlDocument = $objSmarty->fetch("example.tpl");
?>
example.tpl
<html>
<head><title>example</title></head>
<body>
{* テンプレート内でSmartyの機能を使う時は,中括弧で囲みます *}
{$body_message}<br />
{$var_body_message}
</body>
</html>
表示
<html>
<head><title>example</title></head>
<body>
テスト<br />
変数テスト
</body>
</html>

コメント

Smartyのコマンドはテンプレートファイル内で"{ }"で囲むが,
その両端にアスタリスクを書く.
{* テスト *}

変数の表記

マニュアルが英語のままだったのでメモ的に表にしてみる.
表記*2
$foo配列やオブジェクトではない単純な変数を表示
$foo[4]ゼロから始まる配列の5番目の要素を表示
$foo.barPHPで言う所の$foo['bar'].配列$foo*3をassignする
$foo.$barPHPで言う所の$foo[$bar]配列*4のキーを変数で表記
$foo->barオブジェクト$fooのプロパティbarを表示
$foo->bar()オブジェクト$fooのメソッドbar()を実行した結果を表示
$smarty.config.foo設定ファイル*5の変数fooを表示
#foo#$smarty.config.fooの簡易表記
$foo[bar]{section}{/section}内でのみ有効.詳細は繰り返しについて説明する所で
これらを組み合わせて様々な表記が可能
  • $foo.bar.baz
  • $foo.$bar.$baz
  • $foo[4].baz
  • $foo[4].$baz
  • $foo.bar.baz[4]
  • $foo->bar($baz,2,$bar) <-- メソッドにパラメータを渡す*6
  • "foo" <-- 定数も使える

*2 : 全て{ }で囲んで使うことを前提に書いてます

*3 : 連想配列と言いたいけど……

*4 : 連想配列

*5 : Smartyの?

*6 : そのパラメータに変数も使える

連想配列によるassignの一括指定

連想配列のみを渡すことで,そのキーと同じ名前の変数にそのキーに対応する値を割り当てることができます.

example.php
include("Smarty.class.php");

$objSmarty = new Smarty();

$variables = array();
$variables["first"] = "いっこめ";
$variables["second"] = "にこめ";
$variables["third"] = "さんこめ";

$objSmarty->assign($variables);
$objSmarty->display("example.tpl");
example.tpl
{$first}{$second}{$third}
表示
いっこめにこめさんこめ

これを使ってあるオブジェクトのプロパティを一括してテンプレートに割り当てることができます.
include("Smarty.class.php");

$objSmarty = new Smarty();

$bar = new foo();

// get_object_varsは,オブジェクトの持つpublicなプロパティの
// プロパティ名をキー,その値を値とする連想配列を取得する
$variables = get_object_vars($bar);
$objSmarty->assign($variables);
$objSmarty->display("example.tpl");

class foo {
	public $first;
	public $second;
	public $third;
	
	public function __construct() {
		$this->first  = "いっこめ";
		$this->second = "にこめ";
		$this->third  = "さんこめ";
	}
}
詳しい説明はこことかで*7

*7 : オブジェクトをassignして{$object->member}でアクセスする形にしてもいいと後で思った.

繰り返し

{section}{/section}を使う.
nameは,ループ変数兼そのループを識別するための名前*8
ループ変数としては,{section}{/section}の中でのみ有効.
loopには,何回ループするかを決める値を指定する.
配列の最大インデックスを指定するのでは無い点に注意.
例えばArray("a","b","c","d")を考える.
この場合,インデックスの範囲が0-3で長さが4だが,
仮に定数で指定するならloopに指定すべきなのは4になる.

その他指定できる属性
属性名概要
startintegerループ開始番号.
0より小さい値を指定すると末尾から順番に頭に向かって数値が減っていく.
例えば-1なら最後尾.-2なら最後尾から2番目.
startとstepを-1にすると末尾から頭に向かうループになる*9.
stepintegerループごとにループカウンタがどれだけ増減するか
maxintegerループ回数の最大値.ループ回数はloopで決定するが,
それを配列の要素数などで動的な値にした時のリミッターになる.
showbooleanセクション自体を表示するか否か
example.php
<?
include("Smarty.class.php");

$objSmarty = new Smarty();

$arraySource = Array("a","b","c","d");
$loopMax     = 3;

$objSmarty->assign('arraySource', $arraySource);
$objSmarty->assign('loopMax', $loopMax);

$objSmarty->display("example.tpl");
?>
example.tpl
// ループ回数を定数で指定
constant:
{section name=idx loop=2}
	{$arraySource[idx]}
{/section}

// ループ回数を変数で指定
variable:
{section name=idx loop=$loopMax}
	{$arraySource[idx]}
{/section}

// 配列の要素数分ループ
array:
{section name=idx loop=$arraySource}
	{$arraySource[idx]}
{/section}
表示
constant:ab
variable:abc
array   :abcd
おまけ foreach
{* PHP の foreach($myArray as $foo) に対応 *}
{foreach from=$myArray item=foo}
    {$foo}
{/foreach}

section変数

{section}{/section}の処理中に現在の状況に応じて値がセットされる変数群.
section内で参照できる.
項目内容
index現在のループのインデックス
index_prev前回のループでのインデックス(初回は-1)
index_next前回のループでのインデックス
iteration現在のループが反復された回数
回数なので1回目のループでは1
rownumiterationのエイリアス
first現在のループが1回目ならtrueになる
last現在のループが最後ならtrueになる
loopセクションが最後に処理したループのインデックス
セクションの外でも使えるのが特徴
showsectionの属性のshowと同じ
totalセクションがループした合計回数*10
参照方法は,こんな感じ.
$smarty.section.セクション名((nameで指定した文字列)).項目名

//例
<section name=sample>
	{$smarty.section.sample.index}
</section>

繰り返しと連想配列の併用

連想配列を配列にして{section}{/section}でループにかけると便利.
get_object_varsと併用すれば構造体の配列をassignするような感じにできる.

example.php
<?
include("Smarty.class.php");

$objSmarty = new Smarty();

// 連想配列の配列
$masterArray = Array();
$masterArray[] = Array("name" => "apple" , "date" => "2007/08/21","comment" => "りんご");
$masterArray[] = Array("name" => "orange", "date" => "2006/08/21","comment" => "オレンジ");
$masterArray[] = Array("name" => "cherry", "date" => "2003/08/21","comment" => "チェリー");

// 見ての通り,別にテンプレートの変数名とPHPの変数名が一致する必要は無いです
$objSmarty->assign('arraySource', $masterArray);

$objSmarty->display("example.tpl");
?>
example.tpl
{section name=idx loop=$arraySource}
	名前  :{$arraySource[idx].name}<br>
	登録日 :{$arraySource[idx].date}<br>
	コメント:{$arraySource[idx].comment}<br>
{/section}

表示
名前  :apple<br>
登録日 :2007/08/21<br>
コメント:りんご<br>
名前  :orange<br>
登録日 :2006/08/21<br>
コメント:オレンジ<br>
名前  :cherry<br>
登録日 :2003/08/21<br>
コメント:チェリー<br>

{sectionelse}

loop=に設定した変数が有効でない時にこのブロックが表示される.
sectionの属性でshow=falseを指定していた場合にも表示される.

example.php
<?
include("Smarty.class.php");

$objSmarty = new Smarty();

$firstArray = Array("a","b","c","d");
$secondArray = Array("a","b","c","d");

$objSmarty->assign('firstArray', $firstArray);
$objSmarty->assign('secondArray', $secondArray);


$loopMax = 3;

// unsetして変数が無効になるとsectionelseが表示される
unset($loopMax);

// ここでassignしなかったらsectionelseが表示される
$objSmarty->assign('loopMax', $loopMax);

$objSmarty->display("example.tpl");
?>
example.tpl
{section name=idx loop=$loopMax}
	first:{$arrayFirst[idx]}<br>
	second:{$arraySecond[idx]}<br>
{sectionelse}
	ループ回数が不明です
{/section}
表示
ループ回数が不明です

*8 : 識別する名前であると気付いたのが遅かったのでここのサンプルはnameにidxとか適当な名前が入ってしまってます.

*9 : 注意すべきはこの時のloop属性.間違ってはいけないのはloopは,あくまでループ回数を指定しているわけでloopの値になるまでループするとかでは無い.なので末尾からのループだからloop=0にしなければいけないというのは間違い.

*10 : リファレンスによると参照した時点での累計ループ回数が入るはずですが,私が試した時にはループ回数が表示されました.別の環境で再現するかは確認してません.

条件分岐

PHPと同じ感覚でOK.
演算子の両端には必ず空白が必要
{if $name == "apple"}
...
{elseif $name == "orange"}
...
{else}
...
{/if}
使える論理演算子はここを参照

良く使うのはこれ
 ==
!=
>
<
>=
<=
 ===
! //否定
% //剰余
&& (and)
|| (or)
テーブルを作ったりする時に便利なのがこれ
演算子意味
is [not] div by割り切れる
is [not] even偶数である
is [not] even byよくわからない*11
is [not] odd奇数である
is [not] odd byis [not] even byの奇数版

*11 : PHPに直すと($a is even by $b)は(($a / $b) % 2 == 0)らしい

色々

JavaScriptやCSSを埋め込む

Smartyの制御記号である"{ }"が入ってしまうのでそのままではエラーになる.
{literal}{/literal}で囲めばOK.
{literal}
<script language="JavaScript">
<!--
function navigate(path){
	window.navigate(path);
}
// -->
</script>
{/literal}

日付を選択するコンボボックス

日付選択のコンボボックスを埋め込むには,テンプレートで使用可能な関数を使う.
公式マニュアル - html_select_date

デフォルトでは,年月日全てを表示するが,ここでは表示時点の年から向こう3年間を表示する

example.tpl
{* 相対値を指定するには,+や-を付ける *}
{html_select_date display_days=false display_months=false start_year='+0' end_year='+2' prefix='cmbDate'}
表示
<select name="cmbDateYear">
<option label="2007" value="2007" selected="selected">2007</option>
<option label="2008" value="2008">2008</option>
<option label="2009" value="2009">2009</option>
</select>

インデント

Smartyのタグにインデントを埋め込む.
公式マニュアル - indent
例えば上の日付タグの埋め込みで<select>~</select>をタブ3個でまとめてインデントしたい時は次のようにする.
{html_select_date|indent:3:"\t" display_days=false display_months=false start_year='+0' end_year='+2' prefix='dateEnd'}年
この場合,html_select_dateの直後に|indent:3:"\t"を入れているのがポイント.
例えば一番最後に入れると,prefixの部分,つまり<select name="\t\t\tdateEndYear">といった感じでその部分にタブが入ってしまう.
また,html_select_dateの直前にタブを入れていると,そのタブ+指定したタブが<select>の前に付くので形が崩れてしまう.
{html_select_date}の前にタブを入れないようにする以外に方法は無いのかな…….
<option>の前に更にタブを追加する方法は,わからなかった.

デフォルト値の設定

例え変数をassignしていても,初期化していなければデフォルト値が表示される*12

example.php
<?
include("Smarty.class.php");

$objSmarty = new Smarty();

// 値が未定義の変数$foo
$objSmarty->assign("foo", $foo);

$objSmarty->display("example.tpl");
?>
example.tpl
{$foo|default:'初期値'}
表示
初期値

エスケープ

HTMLには,埋め込んではいけないと決まっている文字が存在しますが,
文書をマークアップするためのHTMLなわけで,あらゆる文字を表示できないと困ります.
サニタイズが叫ばれているのも,そのような理由があるわけです*13

そんなわけで,ユーザからの入力を受け付ける時,保存する時,そして出力する時のどこかで
使えない文字を使える文字に変換する処理が必要になるのですが,
出力する時の処理方法の選択肢の一つにSmartyに任せる方法があります.

公式ページ - escape

やり方は簡単.テンプレートで変数を出力する時にescape修飾子を付けるだけ.
{$foo|escape:'html'}

0詰めで表示

string_format修飾子を使います.
{$foo|string_format:"%07s"}
$fooが31なら,表示されるのは0000031.

歯抜けの配列

配列をテンプレートに渡すことを想定します.
ただし,その配列は,空要素(nullの要素)とオブジェクトの要素の二種類が混在しているとします.
この時,ループで空要素に当たった時には何もせず,オブジェクトの要素に当たった時のみ処理を行うにはどうすればいいでしょうか.
公式マニュアルによると,次のようにします.
{section name=testSection loop=10}
  {if isset($sampleArray[testSection])}
    {* こちらにオブジェクトの要素に当たった時の処理を書く *}
  {else}
  {/if}
{/section}
issetを比較時に使えばOK.

数式記法

  • (2008/04/19追加)
  • math
テンプレート内部で算術演算をうまく行えない場合,数式記法を使うと正しく計算されます.
例)
{math equation="x + y" x=1 y=1}
出力)
2
例えば,テンプレート変数と定数のかけ算でピクセル数を設定したい場合の例を挙げます.
<div style="height: {math equation="x / 100 * y" x=$height y=50}px;">
テスト
</div>
この例は,次のように計算されます.
$height / 100 * 50
ちなみに,演算結果を整数部のみにしたい場合は,次のようにsptintf形式のformat属性を指定します.
<div style="height: {math equation="x / 100 * y" x=$height y=50 format=%d}px;">
テスト
</div>

include

テンプレートを分割したい場合があります.
例えば…….
  • 「base.tpl」の中に「field.tpl」に書かれたHTMLを表示し,「field.tpl」の中に「content.tpl」に書かれたHTMLを表示する……
  • 「base.tpl」の上の方に「content.tpl」を表示し,下の方にも「content.tpl」を表示する……
  • Ajaxでページの一部分だけを更新できるようにしたいが,初回表示時には全ての要素をサーバサイドで生成したい……
などなど

PHP側から各テンプレートファイルをfetchメソッドでHTML文字列化し,base.tplに「field.tpl」を埋め込むためのテンプレート変数を用意してそのHTML文字列を埋め込むといったような方法もありますが,include関数を使う方が簡単でメンテナンスしやすい場合もあります.

だいたいの使い方は次のような感じ.
{include file='テンプレートファイルパス' include先にアサインする変数名=アサインする変数の実体}
実際に使うと次のようになります.(ループ内でも使えることも示します)
parent.tpl
parent begin
{section name=testLoop start=0 loop=3}
 {include file='child.tpl' foo=$bar hoge=$fuga count=$smarty.section.testLoop.iteration}
{/section}
parent end
child.tpl
child{$count} begin
変数foo:{$foo}
変数hoge:{$hoge}
child{$count} end
PHP側は次のような形.
$mySmarty = new Smarty();
$mySmarty->assign('bar', '変数bar');
$mySmarty->assign('fuga', '変数fuga');
$mySmarty->display('parent.tpl');
これを実行すると次のように表示されます.
parent begin
child1 begin
変数foo:変数bar
変数hoge:変数fuga
child1 end
child2 begin
変数foo:変数bar
変数hoge:変数fuga
child2 end
child3 begin
変数foo:変数bar
変数hoge:変数fuga
child3 end
parent end
メモ
<input type="text" name="hoge" value="{$hogehoge|escape:'html'}">
<textarea name="fuga"{$fugafuga|escape:'html'}</textarea>
こんな感じでフォームに値を入れる時に例えば<を&lt;に変換して表示しても,
サーバに送られてくる時には元に戻されている*14らしい.
知らなかった……今までどうしてたかな(汗

追記:
ダブルクォーテーション(")がこんな風にエスケープ(\&quot;)されて,これは送信時に元に戻らない.どうしようかな…….

さらに追記:
実体は/Smarty/plugins/modifier.escape.phpで,やってることは
return htmlspecialchars($string, ENT_QUOTES, $char_set);
これだけ.なので変なエスケープは,htmlspecialcharsが原因っぽいけど,そんなバグみたいなのあったっけ?それとも(\&quot;)が正しいのかな.と思いかけたけどそんな変換が正しいわけが無いと思う.

もっと追記:
原因が判明.詳しくは次のウェブページ
PHP 実験室 【覚書 予期しないエスケープ文字】
簡単に書けば,PHPには,セキュリティ強化のために
入力値のシングルクォーテーション,バックスラッシュ,
ダブルクォーテーションの前にバックスラッシュが入る機能があって,
デフォルトでONになっているようです.
OFFにすればOK.
OFFにできない時は,入力値にかかっているaddslashesをstripslashesで排除します.
具体的には,次のページ辺りが参考になります
ただしSQLインジェクションの対策を自分で実装すること.

参考:escape:Smartyをはじめよう! - <や>をそのまま表示
参考:理解されないHTMLエスケープ
参考:URI の中に & を書くべからず

*12 : 空白が表示されるのかなと思ったらこの動作は助かります.

*13 : XSSがどうこう以前に埋め込んではいけない文字は,埋め込んではいけないのです.

*14 : &ltが<に変換されて送られてくる



名前:  非公開コメント   

E-Mail(任意/非公開):
URL(任意):
  • TB-URL  http://mitc.s279.xrea.com/adiary.cgi/023/tb/