タグ別アーカイブ: Twig

Conceptual:WEBサービスページの共通部分をtwigテンプレート化してみる

twigの実践編というわけでもないが、これまでConceptualのページに載せていたWEBサービスページの共通部分(ヘッダー、広告、フッターなど)をtwigテンプレートとして抽出する作業を行ったので、その過程など紹介してみようと思う。

共通部分の抽出

公開しているプログラムのうち、OnlyCSViewEXIFviewerについてはページ構造がほぼ同じだ。現在の2プログラムの画面を並べてみると、下の画像のような感じになる。共通部分と書いてある所はほぼソースが同じであり、真ん中の白い部分(コントローラ部分)とスクリプトの部分だけが違うということになる。

共通部分を持つ2ページ

共通部分を持つ2ページ

テンプレートエンジンで共通部分を抽出する場合、Smarty2.xのような”閉じhead前切り”パラダイムのものだと、ヘッダー部分、メイン部分、フッター部分と必要ファイルが3つになってしまうだろう。今回はテンプレートの継承機能のあるTwigを用いるため、ヘッダーとフッターを含んだ基礎部分のテンプレート(base.html.twig)にメイン部分に対応するブロックを作り、子テンプレートでブロック部分をオーバーライドするという、2ファイルでの解決を図ることにする。

ロジックとビューの分離

加えて、今回手を入れるプログラムは相当昔に作ったものなので、PHP入門書の最初のサンプルにありがちな、ロジックとビューが混在している状態になっている。テンプレートエンジンらしく、これの分離も同時に行ってみたい。

EXIFviewer手入れ前のソース

ロジックとビューの分離の説明に丁度良かったので、今回例として出すのはEXIFviewerの方にする。

 
//手入れ前index.php(1ファイル)
 
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="Description" content="EXIFviewerはローカルのJPEGファイルをアップロードすることなくサイズ、位置情報、撮影情報等のEXIFデータを確認できるWEBサービスです。">
<meta name="Keywords" content="PHP,EXIF,WEBサービス,JPEG,画像,位置情報">
<link rel="stylesheet" href="base.css" type="text/css">
.
.
<script>
$(function(){
$("#filesel").change(function(){
var reader = new FileReader();
reader.readAsDataURL(document.getElementById("filesel").files[0]);
reader.onload = function(e){
$("#path").val(e.target.result);
}
if(window.createObjectURL){
var ourl = window.createObjectURL(document.getElementById("filesel").files[0]);
} else if(window.URL) {
var ourl = window.URL.createObjectURL(document.getElementById("filesel").files[0]);
} else {
var ourl = window.webkitURL.createObjectURL(document.getElementById("filesel").files[0]);
}
$("#viewer").empty().html('<img src="' + ourl + '">');
$("#blob").val(ourl);
});//end of fileselchange
 
//Googlemap関連コード(省略)
 
});//end of jQueryready
</script>
<title>EXIFviewer JPEGファイルのEXIF情報をチェックするWEBサービス</title>
.
.
</head>
<body>
<div id="main">
	<nav><div id="back"><a href="http://akisi.tabiyaku.net/" title="AkisiのWEB制作日記に戻る">AkisiのWEB制作日記に戻る</a></div></nav>
<div id="upperad">
<script type="text/javascript">
 
//adsenseコード
 
</script>
</div>
<div id="instruction">
<h1>セットした画像のEXIFデータを表示</h1>
</div>
<div id="controlls">
<form>
<p><input type="file" id="filesel" type="image/jpeg"></p>
</form>
<form action="index.php" method="post">
<div id = "viewer">
<?php
if(!empty($_POST["path"])){
echo '<img src="' . $_POST["path"] . '">';
}
?>
</div>
<input type="hidden" name="blob" id ="blob">
<input type="hidden" name="path" id ="path">
<p><input type="submit" value="表示" class="btn btn-primary"></p>
</form>
</div>
<div id="tablearea">
<?php
if(!empty($_POST["blob"])){
if($exif = exif_read_data($_POST["blob"],NULL,true)){
echo "<h2>EXIF情報</h2>";
echo "<table class='exiftable table table-striped'><thead><tr><th>キー名</th><th>値</th></tr></thead><tbody>";
foreach ($exif as $key => $section) {
    foreach ($section as $name => $val) {
	if(is_array($val)){
	foreach ($val as $valname => $vval) {
	echo "<tr><td>" . htmlentities($key,ENT_QUOTES,"UTF-8") . ' ' . htmlentities($name,ENT_QUOTES,"UTF-8") . ' ' .  htmlentities($valname,ENT_QUOTES,"UTF-8") . "</td><td id=" . '"' . htmlentities($name . $valname,ENT_QUOTES,"UTF-8") . '">' . "$vval</td></tr>";
	}
	} else {
	echo "<tr><td>" . htmlentities($key,ENT_QUOTES,"UTF-8") . ' ' . htmlentities($name,ENT_QUOTES,"UTF-8") . "</td><td id=" . '"' . htmlentities($name,ENT_QUOTES,"UTF-8") . '">' . htmlentities($val,ENT_QUOTES,"UTF-8") . "</td></tr>";
	}
    }
}
echo "</tbody></table>";
} else {
echo "画像のEXIFデータが見つかりませんでした。";
}
} else {
echo "画像のEXIFデータが見つかりませんでした。";
}
?>
</div>
<div id="googlemapdivarea">
<div id="googlemapdiv"></div>
</div>
<div id="lowerad">
<div class="loweradbox">
<script>
 
//adsenseコード
 
</script>
</div>
<div class="loweradbox">
<script>
 
//adsenseコード
 
</script>
</div>
</div>
</div>
</body>
</html>

いわゆる、初回アクセス時にはPOSTの値(”blob”と”path”)が空であるため例外処理として初期画面を吐いているタイプのページだ。1ファイルでまとまってくれているので扱い易いが、echo命令によってテーブルを構成するhtmlタグの細切れを吐いている部分は一見して訳が分からない。html修正時にミスも起こり易いだろう。

EXIFviewer手入れ後のソース

index.phpにはテンプレートの呼び出しとロジックを押し込み、base.html.twigとindex.html.twigの2ファイルに値だけ渡す

 
//index.php
 
require_once("Twig/Autoloader.php");
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem("twigtmp/");
$twig = new Twig_Environment($loader, array("cache" => "cache/"));
$blob = "";
$exif = array();
if(isset($_POST["blob"])){
$blob = $_POST["blob"];
}
if(!empty($_POST["path"])){
$exif = exif_read_data($_POST["path"],NULL,true);
}
$page = array(
"description" => "EXIFviewerはローカルのJPEGファイルをアップロードすることなくサイズ、位置情報、撮影情報等のEXIFデータを確認できるWEBサービスです。",
"keywords" => "PHP,EXIF,WEBサービス,JPEG,画像,位置情報",
"title" => "EXIFviewer JPEGファイルのEXIF情報をチェックするWEBサービス",
"blob" => $blob,
"exif" => $exif
);
$template = $twig->loadTemplate("index.html.twig");
$template->display($page);

続いて呼ばれる側のテンプレート2ファイル(実際名指しされるのはindex.html.twigの方だけだけれど)。

 
<!-- base.html.twig -->
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/base.css" type="text/css">
{% if description is not empty %}
<meta name="description" content="{{ description }}">
{% else %}
<meta name="description" content="AkisiのWEB制作日記の提供するウェブサービス">
{% endif %}
{% if keywords is not empty %}
<meta name="keywords" content="{{ keywords }}">
{% else %}
<meta name="keywords" content="ウェブサービス,WEB">
{% endif %}
.
.
<title>{{ title }}</title>
{% block head %}
{% endblock head %}
.
.
</head>
<body>
<div id="main">
	<nav><div id="back"><a href="http://akisi.tabiyaku.net/" title="AkisiのWEB制作日記に戻る">AkisiのWEB制作日記に戻る</a></div></nav>
<div id="upperad">
<script>
 
//adsenseコード
 
</script>
</div>
{% block main %}
{% endblock main %}
<div id="lowerad">
<div class="loweradbox">
<script>
 
//adsenseコード
 
</script>
</div>
<div class="loweradbox">
<script>
 
//adsenseコード
 
</script>
</div>
</div>
</div>
</body>
</html>
 
<!-- index.html.twig -->
{% extends "base.html.twig" %}
{% block head %}
<link rel="stylesheet" href="css/index.css" type="text/css">
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
<script type="text/javascript">
$(function(){
 
$("#filesel").change(function(){
var reader = new FileReader();
reader.readAsDataURL(document.getElementById("filesel").files[0]);
reader.onload = function(e){
$("#path").val(e.target.result);
}
if(window.createObjectURL){
var ourl = window.createObjectURL(document.getElementById("filesel").files[0]);
} else if(window.URL) {
var ourl = window.URL.createObjectURL(document.getElementById("filesel").files[0]);
} else {
var ourl = window.webkitURL.createObjectURL(document.getElementById("filesel").files[0]);
}
$("#viewer").empty().html('<img src="' + ourl + '">');
$("#blob").val(ourl);
});//end of fileselchange
 
//Googlemap関連コード(省略)
 
});//end of jQueryready
</script>
{% endblock head %}
{% block main %}
<div id="instruction">
<h1>セットした画像のEXIFデータを表示</h1>
</div>
<div id="controlls">
<form>
<p><input type="file" id="filesel" type="image/jpeg"></p>
</form>
<form action="index.php" method="post">
<div id = "viewer">
{% if blob is not empty %}
<img src="{{ blob }}">
{% endif %}
</div>
<input type="hidden" name="blob" id ="blob">
<input type="hidden" name="path" id ="path">
<p>
<span><input type="submit" value="表示" class="btn btn-primary"></span>
</p>
</form>
</div>
<div id="tablearea">
{% if exif is not empty %}
<h2>EXIF情報</h2>
<table class='exiftable table table-striped'><thead><tr><th>キー名</th><th></th></tr></thead><tbody>
{% for key, section in exif %}
{% for name, val in section %}
{% if val is iterable %}
{% for valname, vval in val %}
<tr><td>{{ key }} {{ name }} {{ valname }}</td><td id="{{ name }}{{ valname }}">{{ vval }}</td></tr>
{% endfor %}
{% else %}
<tr><td>{{ key }} {{ name }}</td><td id="{{ name }}">{{ val }}</td></tr>
{% endif %}
{% endfor %}
{% endfor %}
</tbody></table>
{% else %}
画像のEXIFデータが見つかりませんでした。
{% endif %}
</div>
<div id="googlemapdivarea">
<div id="googlemapdiv"></div>
</div>
{% endblock main %}

テンプレートのcssは、共通部分のためのbase.cssと、index.cssに分ける。手入れ前のソースでは色付きハイライトされているPHP部分がなくなり、その代わりにtwigのfor inで配列を回している。テンプレート関係で新しく登場したのは、for inで回す際にキー値にもアクセスする場合の、{% for key, value in array %}という書き方。それから、配列に要素があるかどうか調べるのには{% if array is iterable %}のように書く、といったことくらいだろうか。
テンプレートの使い分け判断に使う値が変数の中にあるようであれば(あるいは変数自身の存在で判断できるのであれば)変数のみをそのままテンプレートに渡し、そうでないようならロジック側で切り替え判断のためのフラグを値で用意し、変数とフラグを合わせてテンプレートに渡す、このようにしてロジックとビューの分離をはかるわけである。

あとは、テンプレート中のJavaScriptに波括弧が含まれる場合{% raw %}{% endraw %}で囲むのを忘れずに。

TwigをLinuxの公開サーバに上げる段になってハマり易いこと

Twigで個人的にハマったこと。MAMPなどのローカル環境で動作したプログラムを公開サーバにアップロードしてみると、以下のようなエラーが出て動かないということがあった。

Class ‘Twig_Loader_Filesystem’ not found

原因は、ざっくり言ってしまうとLinuxが大文字小文字の扱いにうるさいからだった。たとえばAutoloader.phpへのパスが”twig/Twig/Autoloader.php”だった場合、”Twig/Twig/Autoloader.php”というように指定してしまうと、OSXやWindowsなどの環境では理解してくれるものの、Linuxサーバ上では理解をしてくれない。
また、Autoloader.phpの上位のディレクトリ名も、”Twig”でないといけない。思う所あって”twig”というように小文字のディレクトリにしてアップロードしていたから、”twig/Autoloader.php”という正しいパスを指定してもエラーが出てしまっていることに当惑した。”Twig”ディレクトリは勝手にリネームしてはいけない。それが今回の失敗の教訓。


PHPテンプレートエンジンを使おう Twig編

PHPテンプレートエンジンについて解説・紹介していく企画、素のPHPSmarty2.xSmarty3.xと紹介してきて、満を持してのTwig登場である。

PHPテンプレートエンジンTwigとは

そもそも、Twigとは何なのだろうか。疑問を解決するためにお手元の検索エンジンで検索しても、小枝の画像ばっかり出てくる(笑)。Twigとは、フランスのSensioLabsというソフトウェア会社にスポンサードされている、オープンソースのWEBアプリケーションフレームワークSymfonyを構成する標準バンドルパッケージの一つである(Symfony公式サイトTwig公式サイト)。
WEBアプリケーションフレームワークという言葉が出てきたが、WEBアプリケーションを作るための”概して便利な”枠組みのことで、Symfonyは企業サイトやらAPIやらのWEB上に展開するサービスを作成する際に、ヴァージョンの管理やライブラリの管理、データ構造など様々なことを、MVCの考え方に基づいて整理・管理してくれる仕組みだ。
MVCにのっとっているため、Symfonyは多人数が関わるプロジェクトなどにおいて強みを発揮する。逆に言うと、小規模で作ったら作りっぱなしのようなプロジェクトに導入する意義はそれほど無い。でもそうした小規模プロジェクトでも、Symfonyの特定機能だけ利用したいという需要があるかもしれないので、Symfonyでは機能の多くをモジュール化しており、Symfony本体を導入しなくてもかいつまんで使えるようになっている。そのモジュールの一つが、PHPテンプレートエンジンのTwigというわけだ。

Twigの利用方法

Twig単体で利用する場合、先程紹介したTwig公式サイトより本体を落としてきて、libディレクトリ内のTwigディレクトリを適当な場所に配置する。あとはキャッシュ用のディレクトリと、テンプレートファイル格納用のディレクトリを必要に応じて作っておけばよい。なおインストールにはcomposerというSymfonyのモジュールを使う方法もあり、ファイルの依存関係を管理するというcomposer自体の機能も使いたい場合には、一緒に導入しておいても良いだろう。
で、ひとまずcomposerを使用しないインストールが済んだとして、呼び出し側のPHPスクリプトではこういった感じに書く。Smarty2.xの説明で出した例と同じことをやっているので、適宜比較などしてほしい。

//index.php
 
require_once("Twig/Autoloader.php");
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem("twigtmp/");
$twig = new Twig_Environment($loader, array("cache" => "cache/", "debug" => false));
$template = $twig->loadTemplate("default.html.twig");
$template->display(array("title" => "トップページ"));

まず、先程のTwigディレクトリにあるAutoloader.phpを読み込む。これは関連ファイルを全て読み込むためのクラスで、Twig_Autoloader::registerメソッドを呼ぶ。
次に$loaderに入れているのはテンプレートをロードするクラスのインスタンスで、Twig_Loader_Filesystemの場合コンストラクタにテンプレートファイルのあるディレクトリへのパスを引数として与える。ファイルを読み込むのでなくインラインのStringでテンプレートを用意する場合はTwig_Loader_Stringクラスなど、いくつか種類があるのだが、とりあえずTwig_Loader_Filesystemだけ覚えておけば良い。

次のTwig_Environmentというクラスのインスタンス作成時に、$loaderと配列にした各種設定を渡している。”cache”はキャッシュを入れるディレクトリへのパス、”debug”はデバッグの有無など。キャッシュを使わずデバッグモードもオフならば、第一引数のみで良い。

$templateはTwig_environmentのメソッドであるLoadTemplate()で読み込むテンプレートファイル名を与える。そして$templateのdisplay()メソッドの引数に、変数の値をラベル付き配列で与える。これで標準出力へと出力されることになる。
テンプレート側の記述はこのように。変数の先頭に$をつけず、二重波括弧({{}})で変数名を囲む。

//default.html.twigの記述
 
<!doctype html>
<html lang="ja">
<head>
<title>{{ title }} | サンプル株式会社</title>
</head>
<body>
.
.
</body>
</html>

商品一覧ページをTwigで作ってみる

それでは、Smarty3.xを用いて作った商品一覧ページを、Twigで作り直してみよう。まずはテンプレートファイル側。

 
<!-- base.html.twig -->
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/base.css" type="text/css">
{% if description is not empty %}
<meta name="description" content="{{ description }}">
{% else %}
<meta name="description" content="サンプル株式会社のホームページです">
{% endif %}
{% if keywords is not empty %}
<meta name="keywords" content="{{ keywords }}">
{% else %}
<meta name="keywords" content="サンプル株式会社,小売業,日本">
{% endif %}
<title>{{ title }} | サンプル株式会社</title>
{% block head %}
{% endblock head %}
</head>
<body>
<div id="wrapper">
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="main">
{% block main %}
<article>
<h1>デフォルト文章</h1>
<p>mainのブロックに指定が無い場合のデフォルト文章です。</p>
.
.
</article>
{% endblock main %}
</div>
<footer>
<p>サンプル株式会社 2012-{{ "now"|date("Y") }} all rights reserved.</p>
</footer>
</div>
</body>
</html>
 
<!-- item.html.twig -->
<li>
<section>
<h1>{{ value.name }}</h1>
<p>{{ value.description }}</p>
<p>{{ value.price|number_format(0) }}円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id={{ value.id }}" title="{{ value.name }}の商品詳細">>>商品詳細へ</a></p>
</section>
</li>
 
<!-- itemlist.html.twig -->
{% extends "base.html.twig" %}
{% block main %}
<article>
<h1>サンプル株式会社の商品一覧</h1>
{% if items is empty %}
<p>現在取り扱っている商品はありません</p>
{% else %}
<p>当社が取り扱う商品の一覧です。</p>
<ul id="itemlist">
{% for value in items %}
{% include "item.html.twig" %}
{% endfor %}
</ul>
{% endif %}
</article>
{% endblock main %}

テンプレートファイル側でのSmartyとの違いは、プレースホルダの書き方は先程説明の通り。ifなどの制御構造は括弧だけでなく、end〜という閉じタグになるところや、foreachの書き方がfor inになるところ、blockのname=でなく直接block名指定になるところ、比較演算子がis notのような自然言語的な書き方になることなど、細かい違いが結構あると言える。配列のラベルへのアクセスは同じだが、Twigの場合value[“name”]のようなアクセスも可能。そして、オブジェクトのメンバへのアクセスも同じ書き方で出来る。
修飾子が”|”の後に続くのも同じ。あとは、オシャレなマニュアルでチェックしてほしい。

呼び出し側はこのように。

//itemlist.php
 
require_once("Twig/Autoloader.php");
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem("twigtmp/");
$twig = new Twig_Environment($loader, array("cache" => "cache/", "debug" => false));
$items = array();
 
//例の如く、下記の配列が帰ってきたと仮定
 
$items = array(
array("id" => "1","name" =>"商品A","description" => "さわやかなミントの香りの牛車です(牛は食べられません)","price" => "100000"),
array("id" => "2","name" =>"商品B","description" => "官位です。これがあれば思いのまま(返品不可)","price" => "3000"),
array("id" => "10","name" =>"商品C","description" => "古今和歌集です。新版が出たので在庫一掃セールです","price" => "16000")
);
$page = array(
"description" => "サンプル株式会社の商品一覧ページです。",
"keywords" => "サンプル株式会社,ネットショップ",
"title" => "当社商品の一覧",
"items" => $items
);
$template = $twig->loadTemplate("itemlist.html.twig");
$template->display($page);

まあ既に説明した内容である。displayメソッドに持たせる引数が標準で配列であるため、テンプレート側からそのままラベルのみでアクセスしていることに注意。
あと、この例だとキャッシュを有効化しているので、いじってテストなどする際には、結果確認の前にキャッシュディレクトリの中身を廃棄しておこう(これをわざわざやらなくて良いためにデバッグモードがあるらしい。先程知った)。

SmartyとTwigはどちらが良いのか

以上、Twigについても見てきた。仕組みはそれほどSmartyと変わらないので、乗り換えは楽だろう。ただ、乗り換え後過去資産を活用しようとすると変換は面倒臭いので、そこはあまり期待しない方が良い。
良く言われるSmarty3.xとTwigの速度面の差だが、確かに体感などTwigの方がキビキビ動いている印象がある(Smarty2.xには劣るけど)。速度比較については厳密度は分からないけれど、こんなデータもある。
あとはマニュアルのオシャレさなんかを基準にどちらを使うか決めれば良いだろう。


PHPテンプレートエンジンを使おう Smarty3.x編

前回紹介したSmarty2.xの”閉じhead前切り”パラダイム。htmlを変なところでブツ切りにしなければならない事情は理解できるけれど、やはり少し格好悪い。Dreamweaverのテンプレートと比べると、どうも直感的ではない。また、完成後htmlから切り分けてテンプレートを作っていく形だと、どうしても切り損なってしまい、後から部品テンプレートの数を増やす/減らすなどで、結構な手間が生じる可能性がある。

Smarty3.xのパラダイム テンプレート継承

そこでSmarty3からはDreamweaverのような、テンプレート中の挿入部分をタグで囲み明示する形の仕組みが取り入れられた。この仕組はそして、オブジェクト指向におけるクラスの継承と実に似通ったものとなっている。

<!-- base.tpl -->
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title>{$title} | サンプル株式会社</title>
{block name=head}
{/block}
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
{block name=main}
<article>
<h1>デフォルト文章</h1>
<p>mainのブロックに指定が無い場合のデフォルト文章です。</p>
.
.
</article>
{/block}
</div>
{block name=sidebar}
{/block}
</div>
<footer>
.
.
</footer>
</body>
</html>
 
<!-- sidebar.tpl -->
<div id="sidebar">
<aside>
.
.
</aside>
</div>

{$title}の部分は既に説明済ということで、今回新たに出てきたのは{block}である。{block}で始まり、{/block}で終わる部分は、DreamweaverにおけるTemplateBeginEditableと同じく、ここに記述が追加される可能性がありますよという表示である。では、インデックスページのテンプレートindex.tplをbase.tplの子テンプレートとして作成し、head部にメタタグを1行加えた上でサイドバーのテンプレートsidebar.tplを組み込むといったことを行ってみよう。

<!-- index.tpl -->
{extends file="base.tpl"}
{block name=head}
<meta name="description" content="…">
{/block}
{block name=sidebar}
{include file="sidebar.tpl"}
{/block}

親テンプレートの指定は{extends}で行い、親テンプレート中の{block}に挿入する内容を、子テンプレート内の{block}で囲んでいる。
また、親テンプレート中で{block name=main}で囲まれていた部分は、子テンプレートで指定がされていないため、親テンプレート中で囲まれていたデフォルト値が採用される。
{include}については、テンプレートをテンプレート中で読み込む仕組みになる。勿論サイドバーを読み込まないページについては、サイドバーのブロックを子テンプレート中で無指定にすれば良い。このように部品テンプレートの増減にも柔軟に対応できる。

また実際index.phpから呼び出す際には、子テンプレート名だけ指定すれば、自動的に親テンプレートを継承して表示してくれる。

//index.php
require_once("../bin/php/php5.4.4/lib/php/smarty/libs/Smarty.class.php");
$smarty = new Smarty();
$smarty->template_dir = "../bin/php/php5.4.4/lib/php/smarty/templates";
$smarty->compile_dir = "../bin/php/php5.4.4/lib/php/smarty/templates_c";
$smarty->config_dir = "../bin/php/php5.4.4/lib/php/smarty/configs";
$smarty->cache_dir = "../bin/php/php5.4.4/lib/php/smarty/cache";
$smarty->assign("title","トップページ");
$smarty->display("index.tpl");

Smarty3.xの注意点

Smarty2.xに比べて格段に直感的になったSmarty3.xだが、先述の通り依然Smarty2.xに留まっているユーザも多くいる。その理由としては、PHP5.2以上が必要要件であること、速度面でSmarty2.xにかなり劣るということ、メソッド名の命名規則がキャメルケースになったこと(例:$smarty->clear_all_cache()というメソッドが、$smarty->clearAllCache()に)を始めとして文法の見直しが多少あるということなど挙げられる。
一応、Smarty2.xの書き方と互換をもった、Smarty3.xのBC(Backwards Compatibility Wrapper)というものもあり、Smarty.class.phpの代わりにSmartyBC.class.phpを読み込むことで適用することも可能ではある。

SmartyはJavaScriptの扱いで少し面倒くさい

以上、とりあえずSmartyの基本を説明したわけだが、最初に宣言した通り、個人的にはPHPテンプレートエンジンはSmartyでなくTwigを使っている。その理由については、実は解説を書いている内に思い出したのだが、Smartyの場合、波括弧({})に囲まれた部分は言語構造と見なされてしまうため、JavaScriptとの相性が悪い。JavaScriptを書く部分を{literal}{/literal}で囲んでエスケープするか、あるいはSmarty側の設定をいじって、波括弧でないデリミタをあてがわないといけない。

$smarty->left_delimiter = '<!--{';
$smarty->right_delimiter = '}-->';

たとえばこのようにすると、波括弧の代わりに競合しない括弧の定義が出来るわけである。ただし、競合を避けるためだけに通常より多めの記号を打たされている感がしてあまり心証もよくない。また、熟達したsmarty使いとの引き継ぎの際にやはり口論が起きる。

(追記&訂正:コメントでご指摘いただいたとおり、Smarty3では直前ないし直後に空白文字・改行文字・タブなどを伴う波括弧はデリミタとみなされないという仕様ができたらしい。したがって、CSS、JavaScriptともに波括弧使用の後に改行を行うコーディングスタイルであれば、自然にSmartyの言語構造とみなされることを回避してくれる。

CSSの場合

 
/* 前後に改行文字があるためSmartyの言語構造とみなされない */
* {
margin : 0;
}

JavaScriptの場合

 
//前後に改行文字があるためSmartyの言語構造とみなされない
function someFunction(){
return 0;
}
 
//前後に改行文字があるためSmartyの言語構造とみなされない
while(1){
someFunction();
}

ただ、人によっては無名関数の処理部分など改行せずに記述したいという場合もあるので、その場合には少し手直しが必要になるだろう

JavaScript

 
//こんな記述はアウト
var someFunction = function(){return 0;};
 
//せめてこう変えるべき(スペースが入るのでセーフ)
var someFunction = function(){ return 0; };

追記終わり)

twigの場合だと、Smartyの波括弧にあたるのが”{%”と”%}”なので、特別に設定しなくても競合はそう起こらない。起こる可能性があるとしたら、jquery.tmplの言語構造を囲む二重波括弧({{}})が変数とみなされてしまうケースが考えられるけれど、相対的には少ないので、jquery.tmplを使うところだけ{% raw %}{% endraw %}で囲っておくという形で対処する。熟達したtwig使い(そもそもいないのでは?)との間に口論も起きないだろう。