テンプレートの仕組みは、前回のPHP編で説明したとおり。それでは今度は、テンプレートエンジンという便利なモノについて見ていこう。
自作PHPテンプレートの欠点
前回紹介したPHPテンプレートには、いくらでも改善の余地がある。たとえば本格的に使用するのであれば、プレースホルダを置き換える値として何が来ても大丈夫なよう、エスケープをする必要があるだろうし、商品紹介ページのようなプレースホルダと変数が一対一対応になるページではなく、一対多の関係になるであろう商品一覧ページのようなものを実現するためには、配列からループで値を取り出しプレースホルダ部分にあてはめる仕組みが必要となる。もちろん、熟達した腕をもつPHP使いにとっては朝飯前の作業だろうが、担当者が変わり引き継ぎの可能性がある場合など、前任PHP使いのクセを読み取る無駄な作業ができてしまう(そして熟達したPHP使い同士による引き継ぎだと、口論が起きるだろう(笑))。
オープンソースのテンプレートエンジンを使おう
そこでSmartyなどのオープンソーステンプレートエンジンだ。機能の改善余地となりうるところはコミュニティが既に改善してくれている可能性が高いし、メジャーなテンプレートエンジンであれば記法を一度覚えればつぶしが効く。
また、ポピュラーなテンプレートエンジンというものはMVCを念頭に置いて設計されている可能性が高い。MVCというのは、モデル・ビュー・コントローラのそれぞれの頭文字をとった単語で、プログラムを処理とデータを担当する部分(モデル)、見た目の部分(ビュー)、ユーザインターフェースの部分(コントローラ)に分けて制作するという考え方だ。前回の記事の最後に言及したデザイナー・htmlコーダとの連携は、MVCを意識したプログラムを使うとより容易になるわけで、たとえばモデルの部分の開発作業が滞ってしまったとしても、ビューやコントローラの作業の進行には影響が出なくなる。
Smartyの利用方法(2.x編)
オープンソーステンプレートエンジンの筆頭、Smartyであるが、ヴァージョン2.x系と3.x系では少し使い勝手も異なり、2.x系の速度面での優位も大きいので、いまだ2.x系にとどまっているユーザも多い。2.xのパラダイムから見ていった方が面白いので、今回はSmarty2.xについての紹介のみということにしよう。
まずSmartyの実体とは何かというと、PHPプログラム。したがってPHPプログラムの動作するディレクトリに置けばその機能を利用することができるようになる。インストール方法であるが、公式サイトから2.x系の最新ヴァージョンを落としてきて解凍、libsというディレクトリを取り出してアップロードする。
libsディレクトリと同階層には、4つの空ディレクトリを作る(cache、configs、templates、templates_c)。パブリックディレクトリでこれをやってしまうと散らかってしまいみっともないので、適当にsmartyディレクトリでも作って、その中にlibsを含めた5つのディレクトリを放り込んでおく。
Smartyのディレクトリを作成
もちろんパブリックより上の階層にアクセスする権限があるのなら、このsmartyディレクトリも上の階層に設置して、勝手にアクセスされる危険性を無くしておいた方がよい。MAMPだったらMAMP/bin/php/(使用中のphpのヴァージョン)/lib/phpあたりに入れておけば良いだろう。
ディレクトリの内のcacheとtemplates_cについては、書き込み権限が必要となるので設定する。権限は「770」とか「775」あたりにしておく。OSXの場合は、「情報を見る」から「共有とアクセス権」などで設定できるはずだ。
Smartyによるテンプレート読み込みの基本
Smartyはクラスとして定義されており、利用する際にはSmarty.class.phpをrequireしたあとインスタンスを作り、メソッドを使ってテンプレートと変数の対応関係をつけていくことになる。まずインスタンスの作成,そして各種ディレクトリへのパスを宣言しておく。
//MAMPのMAMP/bin/php/bin/php/php5.4.4/lib/phpディレクトリに
//smartyディレクトリを投げ込んだ場合の例
//htdocs直下の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"; |
//MAMPのMAMP/bin/php/bin/php/php5.4.4/lib/phpディレクトリに
//smartyディレクトリを投げ込んだ場合の例
//htdocs直下の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";
実際パスをいちいち書いていたら冗長に過ぎるので、その辺りは適宜定数として宣言して対応できるだろう。
テンプレートファイルは.tplという拡張子をつけて、templatesディレクトリに放り込んでおく。たとえばdefault.tplというテンプレートファイルを読み込む場合、displayメソッドを使う。
$smarty->display("default.tpl"); |
$smarty->display("default.tpl");
テンプレートのプレースホルダに値をセットする
displayメソッドを呼ぶ前に、テンプレート内のプレースホルダに対して値をセットすることが出来る。たとえば、default.tplのページタイトル部分にプレースホルダを置く場合は、{}で変数名を囲んでおく。
//default.tplの記述
<!doctype html>
<html lang="ja">
<head>
<title>{$title} | サンプル株式会社</title>
</head>
<body>
.
.
</body>
</html> |
//default.tplの記述
<!doctype html>
<html lang="ja">
<head>
<title>{$title} | サンプル株式会社</title>
</head>
<body>
.
.
</body>
</html>
このテンプレートに対して、プレースホルダの変数名を指定してassignメソッドを使い値を入れる。
//トップページ index.phpから呼び出すとき
$smarty->assign("title","トップページ");
$smarty->display("default.tpl");
//会社概要ページ company.phpから呼び出すとき
$smarty->assign("title","会社概要");
$smarty->display("default.tpl"); |
//トップページ index.phpから呼び出すとき
$smarty->assign("title","トップページ");
$smarty->display("default.tpl");
//会社概要ページ company.phpから呼び出すとき
$smarty->assign("title","会社概要");
$smarty->display("default.tpl");
このようにすることで、ユーザがindex.phpのアドレスを指定してアクセスしてきたときにはページタイトルが”トップページ | サンプル株式会社”となり、company.phpにアクセスするときには”会社概要 | サンプル株式会社”となる。そして、全てのページのタイトルにおいてサンプル株式会社の部分を変更したくなったとき、修正するファイルはdefault.php一つだけで済む(なんというテンプレートエンジン!)。
Smarty2.xのパラダイム ”閉じhead前切り”
Smartyのテンプレート表示の基本は上の通り。配列をループで出力したり…といった使い方はまた別の機会に実践編として説明しよう(するかもしれない)。
PHPは命令の実行結果を逐次標準出力に吐き出していくことが出来るので、なにもdisplayメソッドが一ファイル中一度しか使えないわけではない。そのため、htmlファイルを部分部分に分けてそれぞれテンプレート化し、必要に応じて呼び出すと言った方法が取られる。そのため、テンプレートエンジンを使う場合でもまず完成系のhtmlファイルを作成してしまって、部分部分に切っていくという手順が一般的である。前回の例を使って、部分部分に切りわけていこう。
まず元となるhtmlファイルがこれ。
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title>トップページ | サンプル株式会社</title>
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<article>
<h1>ごあいさつ</h1>
<p>ようこそサンプル株式会社ホームページへ!</p>
.
.
</article>
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html> |
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title>トップページ | サンプル株式会社</title>
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<article>
<h1>ごあいさつ</h1>
<p>ようこそサンプル株式会社ホームページへ!</p>
.
.
</article>
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>
タイトル部分やメインコンテンツ部分を各ページ毎に変えたいというのはもちろんのこと、ページ内容によってはサイドバーのない画面いっぱいのデザインも使いたい。ヘッダ画像部分は全ページ共通でよい。という要請の場合、bodyタグの直前部分までと、ヘッダ画像とメイン部分、サイドバー部分、フッター以降html閉じタグまでという4つのテンプレートに分けて作成する。
<!-- head.tpl -->
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title>{$title} | サンプル株式会社</title>
<!-- main.tpl -->
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
{$article}
</div>
<!-- sidebar.tpl -->
<div id="sidebar">
<aside>
.
.
</aside>
</div>
<!-- footer.tpl -->
</div>
<footer>
.
.
</footer>
</body>
</html> |
<!-- head.tpl -->
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title>{$title} | サンプル株式会社</title>
<!-- main.tpl -->
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
{$article}
</div>
<!-- sidebar.tpl -->
<div id="sidebar">
<aside>
.
.
</aside>
</div>
<!-- footer.tpl -->
</div>
<footer>
.
.
</footer>
</body>
</html>
まあ、こんな形で、呼び出し側ではこのようになる。
//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("head.tpl");
$smarty->assign("article","<article>
<h1>ごあいさつ</h1>
<p>ようこそサンプル株式会社ホームページへ!</p>
.
.
</article>");
$smarty->display("main.tpl");
$smarty->display("sidebar.tpl");
$smarty->display("footer.tpl"); |
//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("head.tpl");
$smarty->assign("article","<article>
<h1>ごあいさつ</h1>
<p>ようこそサンプル株式会社ホームページへ!</p>
.
.
</article>");
$smarty->display("main.tpl");
$smarty->display("sidebar.tpl");
$smarty->display("footer.tpl");
切り慣れていないので、本当はもっと効率の良い切り方はあるのだろうけど。
で、強調したいところはmain.tplが何故bodyタグから始まらずに、閉じheadタグから始まるかというところ。htmlコーダの人は、この閉じheadタグの所在をかなり不気味に思うだろう。
実はこれ、各ページによってcssファイルの読み込みやscriptタグでのjavascriptの記述、あるいはそのページ特有のmetaタグ(meta descriptionなど)が入り込む可能性を考慮してこの切り方になっている。その場合header.tplとmain.tplのdisplayの間で、また別のテンプレートを呼び出すか、あるいはインラインで書くか。とにかく急に1ページだけライブラリの読み込みが必要となった場合などに、全ページ共通読み込み部分のhead.tplの記述を増やさなくてよいような配慮となっている。
このような配慮を考える必要があるのは、部品化はできても部品同士の上下関係はPHPスクリプトにおける呼び出し順に依拠するためだ。ということで、こういうテンプレートエンジンのパラダイムを、勝手に”閉じhead前切り”パラダイムと命名。Smarty 2.xは”閉じhead前切り”パラダイム!