カテゴリー別アーカイブ: 汎用テク覚え書き

Smartyのforeachを使って商品一覧ページテンプレートなどを実現

Smartyユーザでもない人間が思い出しながらSmartyとテンプレートの良さについて解説するシリーズ(前回Smarty3.x編前々回Smarty2.x編)。前回までの例だと、いまいちテンプレートエンジンの必要性が伝わりにくい。そこで、今回はテンプレートエンジンが輝く例として、ありきたりではあるが商品の一覧ページを作る場合などについて解説していこう。

Smartyのforeachで、配列の要素数だけ繰り返し出力

オンラインショップを運営していて、商品の削除や追加があった場合、商品一覧ページから商品を消したり既存のリスト項目のhtmlをコピーして内容だけ変えて追記したりと、静的な仕組みの場合には都度の手間がかかってしまう。そこで、商品をデータベースに追加したら自動的に商品一覧ページに追加、表示してくれるような仕組みがあると有り難い。
勿論そうした仕組みを実現するには、データベースの知識などが必要である。まあ、その点はクリア出来たとして(きっと”SELECT * FROM table WHERE〜”とかなんとかSQLで取得したとして)、次に手元に出来た配列の要素数だけリスト項目を作ろうという段階になるわけだ。
データベースから帰ってくる結果の数については、もちろんテンプレートを用意する段階では知るよしもない。そこで、配列の要素数を読み取ってその回数だけループさせるための言語構造、foreachを使う。

 
//{foreach}から{/foreach}までの部分を、$arrayの要素数だけ繰り返す。
//配列のキー名はkey=で取り出す。
//値はitem=で取り出す。
//いずれも与えた文字列と同じ名前の変数({$key}、{$value}など)で取り出す。
 
<ul>
{foreach from=$array key="key" item="value"}
<li>キー値{$key}の値は{$value}</li>
{/foreach}
</ul>

たとえば$array = array(“山”,”川”,”谷”);という宣言の後に上のforeach部分が解釈されると、出力htmlは以下のようになる。

<ul>
<li>キー値1の値は山。</li>
<li>キー値2の値は川。</li>
<li>キー値3の値は谷。</li>
</ul>

このように、配列の要素数だけのリスト項目を出力してくれる。

Smartyではテンプレート自体に条件分岐を埋められる

ただ、上の例は実は要素数が0の場合に対応できていない。要素数が0だと、リスト項目をもたないただのulタグのみが出力されてしまい、htmlの文法エラーとなってしまう。
そこで、配列の要素数が0の場合ulタグも出力しないといったような条件分岐もつけたくなる。Smartyのテンプレート側でその対応は可能で、{if}内でPHP関数empty()を用いて出力の調整が出来る。

{if empty($array)}
<p>項目がありません。</p>
{else}
<ul>
{foreach from=$array key="key" item="value"}
<li>項目名{$key}の値は{$value}</li>
{/foreach}
</ul>
{/if}

このようにロジックを埋め込める。テンプレート側にロジックを埋め込むのはMVCの観点上あまりよろしいことではないのだが、こういった対応も可能ということで。

実際に商品一覧ページをテンプレートにしてみる

それでは、実際に例として商品一覧ページを作ってみよう。今回例に使うのはSmarty3.xなので、そこのところご注意を。

 
<!-- base.tpl -->
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/base.css" type="text/css">
{if $page.description != ""}
<meta name="description" content="{$page.description}">
{else}
<meta name="description" content="サンプル株式会社のホームページです">
{/if}
{if $page.keywords != ""}
<meta name="keywords" content="{$page.keywords}">
{else}
<meta name="keywords" content="サンプル株式会社,小売業,日本">
{/if}
<title>{$page.title} | サンプル株式会社</title>
{block name=head}
{/block}
</head>
<body>
<div id="wrapper">
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="main">
{block name=main}
<article>
<h1>デフォルト文章</h1>
<p>mainのブロックに指定が無い場合のデフォルト文章です。</p>
.
.
</article>
{/block}
</div>
<footer>
<p>サンプル株式会社 2012-{$smarty.now|date_format:"%Y"} all rights reserved.</p>
</footer>
</div>
</body>
</html>
 
<!-- item.tpl -->
<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.tpl -->
{extends file="base.tpl"}
{block name=main}
<article>
<h1>サンプル株式会社の商品一覧</h1>
{if empty($page.items)}
<p>現在取り扱っている商品はありません</p>
{else}
<p>当社が取り扱う商品の一覧です。</p>
<ul id="itemlist">
{foreach from=$page.items item="value"}
{include file="item.tpl"}
{/foreach}
</ul>
{/if}
</article>
{/block}

base.tplはどのページにも共通するベースのテンプレート。item.tplが商品情報と個別ページへと飛ぶリンクをのせる枠組みで、itemlist.tplというのは商品一覧ページitemlist.phpが呼ばれた時に読み込むテンプレート。このitemlist.tplがbase.tplの子テンプレートになっているわけだ。というわけで、呼び出し側のPHP。Smartyのパス等はdefinesmarty.phpという別ファイルにまとめたという設定で。

//itemlist.php
 
require_once("script/definesmarty.php");
$items = array();
 
//本来$itemsにデータベースから取ってきた商品情報を入れる処理が入る。
//ここでは以下の情報がかえってきたものとする。
 
$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" => "160000")
);
$page = array(
"description" => "サンプル株式会社の商品一覧ページです。",
"keywords" => "サンプル株式会社,ネットショップ",
"title" => "当社商品の一覧",
"items" => $items
);
$smarty->assign("page",$page);
$smarty->display("itemlist.tpl");

色々と新しい表現などが登場しているが、とりあえず出力はこんな感じだ。

itemlist.phpへアクセスした結果

itemlist.phpへアクセスした結果

テンプレート内で使える、Smartyの便利な機能

前回までの例と異なり、今回はassignで連想配列を与えている。連想配列のキーで値にアクセスするためには、変数の後ろにピリオドをつける。これはまあ、説明しなくてもなんとなく分かってもらえるだろう。オブジェクトのメンバの場合には、標準的なPHPの場合と同じく”->”でアクセスする。
次に、所々に出てくる、変数の後ろに”|”がついているもの。これは後ろに続くのが修飾子名で、変数を展開した後のテキスト処理を行ってくれる。例で挙げているもののうち、”number_format:0″という修飾子。これは数字の3桁毎にカンマを入れてくれている。したがって出力後の数字は100,000円などとなっているはずだ。この修飾子のさらなる使い方は、PHPマニュアルでも見てほしい。
そして、フッタ部クレジットにある$smarty.nowというのは、Smartyが標準で備える予約変数で、プログラム動作時のタイムスタンプを得る。例のように書いておけば、年が明けてもクレジットの年号を変更する必要が無くなるわけだ。

如何だっただろうか。Smartyを使えばこのように簡単にテンプレートを用いた開発が出来る。究極足りないところはインラインPHPで補いながら開発ができるので、Dreamweaverのテンプレートより便利だろう。
こんなに便利なSmartyだが、惜しむらくはやはりヴァージョン2と3のユーザが混在しており、WEBで調べながら開発していく方法においてはリファレンスがどちらのヴァージョンに基づいたものなのか分かりにくいということ。あと、公式マニュアル(Bitcoinがドネートできる!)のデザインが無骨。確かTwigに乗り換えた理由の1つは、マニュアルがオシャレだったから、とどうでも良いことを思い出したり。


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使い(そもそもいないのでは?)との間に口論も起きないだろう。


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

テンプレートの仕組みは、前回の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のディレクトリを作成

もちろんパブリックより上の階層にアクセスする権限があるのなら、この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";

実際パスをいちいち書いていたら冗長に過ぎるので、その辺りは適宜定数として宣言して対応できるだろう。
テンプレートファイルは.tplという拡張子をつけて、templatesディレクトリに放り込んでおく。たとえばdefault.tplというテンプレートファイルを読み込む場合、displayメソッドを使う。

$smarty->display("default.tpl");

テンプレートのプレースホルダに値をセットする

displayメソッドを呼ぶ前に、テンプレート内のプレースホルダに対して値をセットすることが出来る。たとえば、default.tplのページタイトル部分にプレースホルダを置く場合は、{}で変数名を囲んでおく。

//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のアドレスを指定してアクセスしてきたときにはページタイトルが”トップページ | サンプル株式会社”となり、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>

タイトル部分やメインコンテンツ部分を各ページ毎に変えたいというのはもちろんのこと、ページ内容によってはサイドバーのない画面いっぱいのデザインも使いたい。ヘッダ画像部分は全ページ共通でよい。という要請の場合、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>

まあ、こんな形で、呼び出し側ではこのようになる。

//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前切り”パラダイム!


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

このブログの作成当初、「作成するサイトにはテンプレートエンジンとしてSmartyを採用します!」とか言ってしまっているのだけれど、現在のところ結局Twigに落ち着いてしまっている。いや、色々と理由があったのだけれども、それらをすっ飛ばして、当初の宣言を無かった事にしてTwigの紹介を始めても、というところがあるので、本当にさらっとテンプレートエンジンとは何かと、諸事情で通過してしまったSmartyについて書こうと思う。

テンプレートの考え方

というわけで、まずテンプレートとは何か。テンプレートというのは、Dreamweaverを始めとして数々のPCアプリケーションではそのままの単語が使用されている、作成する書類の「ひな形」の事だ。つまりもう文書構造まではでき上がっているファイルに、タイトルだの本文だのオリジナルな情報を加えて保存すると、立派な当たり障りの無いファイルが出来る。また、オリジナル情報の部分を書き換える事で、似たような文書を量産する事が出来る、そういった仕組みだ。

WEBサイトを構成する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>サンプル株式会社 商品A</title>
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<article>
<h1>商品Aの概要</h1>
<p>説明説明説明</p>
.
.
</article>
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>

この2つのページは、ほぼ共通部分によって構成されている。違いと言えば、ページタイトルとid=”main”の中のarticleだけだ。
テンプレートを使わない方法では、たとえばテキストファイルでページタイトルとarticle以外の部分を空白にした文書を作っておき、新しくページを加える際にはそのファイルを元にして新ページのファイルを書くといった手順になるだろう。
けれども、この方法だと全ページのサイドバーに項目を増やしたいなどとなったとき、テキストファイルを開いてコピー、開いてコピーと忙しい。非現実的である。

Dreamweaverにおけるテンプレート

そこで、Dreamweaverなんかではテンプレートファイル.dwtを作成して、テンプレート全体に渡る変更はこの.dwtファイルを弄れば良いというような仕組みでやっている。

 
<!-- テンプレートファイル.dwt -->
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<!-- TemplateBeginEditable name="doctitle" -->
<!-- TemplateEndEditable -->
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<!-- TemplateBeginEditable name="mainarea" -->
<!-- TemplateEndEditable -->
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>

このような.dwtファイルを作成しておき、新しくページを作成する際はこのテンプレートを元にTemplateBeginEditable…というhtmlコメントアウトとTemplateEndEditable…に挟まれた部分にページオリジナルのコンテンツを入れる。もしテンプレート自体に変更が起きた場合は、そのテンプレートを元に作成された全てのページへの同様の変更が有効になるといった具合だ。

PHPでテンプレート方式を利用する

でも、Dreamweaverはお高い。現在ではCreative Cloudなんかで継続的にコストがかかるし、契約を止めてしまえば作成した.dwtファイルなんて無用の長物になってしまう。そこで、テンプレート機能のためだけにDreamweaverを導入するくらいならば、プログラムで何とかしてしてしまおうという考え方もあるだろう。
幸いPHPにはテンプレート方式を利用するのに具合の良い仕組みがある。一番大きいのは、html文書の途中にインラインで出力命令を埋め込めること。そこで、実際ページ毎に出力が異なる部分にあらかじめecho命令を書いておいたものをテンプレートファイルとして保存し、各ページのアドレスに対応するファイルから変数に出力する値を設定して呼び出せば良いのだ。

 
//テンプレートファイル template.php
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
<title><?php echo $title; ?></title>
</head>
<body>
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="wrapper">
<div id="main">
<?php echo $honbun; ?>
</div>
<div id="sidebar">
<aside>
.
.
</aside>
</div>
</div>
<footer>
.
.
</footer>
</body>
</html>
 
//トップページ index.php
<?php
$title = "サンプル株式会社 トップページ";
$honbun = "<article>
<h1>ごあいさつ</h1>
<p>ようこそサンプル株式会社ホームページへ!</p>
.
.
</article>";
include("template.php");
?>
 
//各商品紹介ページ itemA.php
<?php
$title = "サンプル株式会社 商品A";
$honbun = "<article>
<h1>商品Aの概要</h1>
<p>説明説明説明</p>
.
.
</article>";
include("template.php");
?>

ショートタグを使えばプレースホルダに近く

呼び出し側のファイルでは、変数を参照する箇所で毎度毎度インラインのPHPを呼び出し、echo命令を打っている。このままでも別に構わないのだろうが、よりテンプレートっぽくするために、プレースホルダに近い省略表現にしてみよう。
プレースホルダというのは、テンプレートエンジンで使われる用語の一つで、後から何か別のものを置く場所に仮置きしておくものである。Dreamweaverではイメージプレースホルダーという名前の、仮置き用のイメージをおける機能があるが、これはとりあえず関係ない。先の例でechoを使っていた部分を、そこに吐き出す変数名とプレースホルダであることを示すちょっとした目印表現の組み合わせ程度に簡略化すれば、仮置きらしくなる。
PHPは本当にテンプレートに向いている言語というべきか、echo命令の同義語として以下のような変換が出来る。

//これが
<?php echo $title; ?>
 
//これと同義語に
<?= $title ?>

幾分短くなった。この書き換えは、PHP5.4以降のヴァージョンなら標準で、5.3以前のヴァージョンなら、php.iniを書き換えてshort_open_tagをOnにすればできる(残念ながらini_setでは不可能)。
テンプレート使用のメリットの一つとして、デザイナーとプログラマーの作業の分担というものがある。PHPは全く分からないというデザイナー・htmlコーダにテンプレートファイルを渡して、”<?=”と”?>”に囲まれている部分には絶対に手を付けるなと言っておけば、デザイン作業が進みつつ、プログラマも並行してプログラムを書くことが出来る。ということで、テンプレート導入は、WEB制作の大きなパラダイムとなったわけだ。


標準出力のバッファリングを有効化するPHP関数ob_start

PHPは、WEBブラウザという手近な実行環境を持つプログラミング言語です。そして、WEBブラウザ以外にも実行環境を持つプログラミング言語です。WEB制作の目的でPHPを習得しており、かつPHPが初めてのプログラミング言語である場合などには気付かないかもしれませんが、コマンドライン上でPHPを動かすことも可能なわけです。
したがって、echo命令等が出力する先は、常にWEBブラウザのウィンドウというわけではありません(そもそもWEBブラウザで動いていないPHPがどこのウィンドウに出力するというのでしょう)。echo命令のマニュアルに書いてあるように、出力先はあくまで”標準出力”となっています。

標準出力をバッファリングする

さて、WEBブラウザ上でPHPを実行する場合、この”標準出力”として、WEBブラウザ上のウィンドウが指定してあります。そのおかげあってecho命令はいつもブラウザ上に文字列を吐き出すわけですが、echo命令が吐き出す内容を一時的に変数に入れたい場合や、ファイルに保存したい場合などは、プログラム中でタイミングを指定して、標準出力のバッファリングを行うことが可能です。
標準出力バッファリングのための関数には、output bufferingの略、ob_という接頭辞がついています。プログラム中のバッファリングを開始したいポイントでob_start関数をコールし、バッファリングを停止したいポイントでob_end_clean関数ないしob_end_flush関数を呼びます。

<?php
echo "a";
ob_start();
echo "b";
echo "c";
ob_end_clean();
echo "d";
?>

このプログラムをWEBブラウザで走らせると、ブラウザのウィンドウには”ad”と出力されます。ob_startとob_end_clean関数に挟まれている部分で出力されるべき”bc”は、その間標準出力がジャックされているので表示されません。
一方、終了時の関数をob_end_cleanではなくob_end_flushにした場合、バッファリングの終了時に貯めていた出力内容を標準出力に開放します。そこで、ブラウザの出力は”abcd”となります。

バッファリングした内容を変数に格納する

バッファリングした内容を、そのまま破棄あるいは標準出力に出力してしまうのも面白くありません。ob_get_contentsという関数を使い、変数に格納して取り出してみましょう。

<?php
echo "a";
ob_start();
echo "b";
$buffer = ob_get_contents();
echo "c";
ob_end_clean();
echo "d";
echo $buffer;
?>

このプログラムの出力結果は”adb”となります。ob_get_contents関数が呼ばれた時点でのバッファを変数に格納し、echo “d”;の後に吐き出した結果ですね。

応用:他ファイルでのPHPスクリプト実行結果を読み込む

このようなバッファリングが役に立つケースとして、他ファイルでのPHPスクリプトの、”実行結果”を読み込み、変数に格納しておきたい場合が挙げられます。
たとえばWEBページのコンテンツ部分のHTMLを、PHPスクリプトを使って動的に生成する場合について考えます。仮に、contents.phpというファイルを作成していた場合、呼び出し側の記述は以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
.
.
</head>
<body>
.
.
<div id="contents">
<?php include("contents.php"); ?>
</div>
.
.
</body>
</html>

この例の場合、includeは1回で済んでいますが、contents.phpの生成内容を同じページ内で何度も繰り返す必要がある場合、都度includeするのではなく、変数に格納しておいた方がよいということになります。

<?php
ob_start();
include("contents.php");
$contents = ob_get_contents();
ob_end_clean();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
</head>
<body>
.
.
<div id="contents1">
<?php echo $contents; ?>
</div>
<div id="contents2">
<?php echo $contents; ?>
</div>
.
.
</body>
</html>

また、ページによってどのファイルを呼び出すか、分岐の可能性がある場合などには、この書き方ではincludeの周辺が分岐ロジックの記述などで膨れ上がってしまい、呼び出し側のHTMLの視認性を損ないます。それを避けるため、コンテンツを呼び出す部分では変数の出力にとどめ、変数に入れる値をPHPのロジック部分にてあらかじめ求めておくといった形にする方がベターです。その場合にも、出力バッファ関数が役に立ちます。

<?php
ob_start();
if("条件式"){
include("contents1.php");
} else {
include("contents2.php");
}
$contents = ob_get_contents();
ob_end_clean();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
.
.
</head>
<body>
.
.
<div id="contents">
<?php echo $contents; ?>
</div>
.
.
</body>
</html>

その他、色々とアクロバティックなコードを書くときに使えたりします。もちろん推奨はしませんが(笑)。


Webフォントにそろそろ手を出すの事

WordPressでは、ヴァージョン3.6とTwenty Thirteenのペアから本格的採用が始まったWebフォント(WebFonts)。WordPressでの新技術採用は見切り発車というか地固めというか、1年くらいして各社ブラウザがWordPressに合わせてくるのを待って、次第に導入するのが都合が良い。HTML5然り、レスポンシブデザイン然り。それで、Twenty Thirteenの登場からは1年というタイミング、そろそろ趣味のサイト以外でも採用して良い頃合いなのではないかなと思ったので、なるべく安牌的な導入方法を調べてみることにした。

Webフォントの概要

Webフォントというのは、従来のようにユーザのローカル環境にあるフォントを引っ張ってきてWEBコンテンツを表示させるのではなく、WEB制作者側があらかじめ指定したフォントをユーザ側にダウンロードさせて、ある程度制作者側が想定した通りの表示内容で表示させる仕組み。この仕組みがあれば、font-family : sans-serif;のように指定した結果がWindows環境とMac環境で全く異なって表示されてしまうという、デザイン上の困り事も軽減される。

採用のデメリットは、Webページが重くなること。何しろフォントセットを丸々ダウンロードさせるわけで、特にモバイル環境のユーザに対してはストレスを与える結果になる事が多い。また、サイトの表示が重くなると、SEO的観点からもあまりよろしくない。この辺りは膨大な文字数が必要な日本語フォントでは、とりわけ顕著である問題。

Webフォントの仕組みはCSS3の正式規格で、これ自体をサポートしていないブラウザは切り捨てる方向で話を進める。こういう話のときに取り残されるブラウザは大抵IEなのだが、実は元々WebフォントはIEの拡張規格から来ているので、案外IE4などでも使えたりする。ただ、IE8以前の場合はフォントの規格自体がEOTという独自路線のみ対応なので、場合によっては切り捨ててしまった方が面倒でないだろう。

Webフォントとして使用できる形式

そう、どのタイプのフォントがWebフォントとして使用できるかは、ブラウザ側の対応状況に依存する。TrueTypeフォントやOpenTypeフォントはPC環境に付属する一般的なフォントだが、Firefox、Chrome、Safariともにかなり以前のヴァージョンから対応している。また、Mobile SafariではiOSの4.2から対応している。日本発売のiOSデバイスならば、上限までアップデートすれば全デバイスで表示が可能ということだ。
TrueTypeやOpenTypeを表示できないのが、IE。先程も書いた通り、一貫してEOT形式を採用してきた。そのため、表示したいフォントがTrueTypeやOpenTypeであった場合、EOTに変換した上で、同じフォントをIE用に指定して読み込ませるという方針が考えられる。
TrueTypeフォントのEOTへの変換は、WEBサービスやローカルアプリケーションなどが検索すれば出てくる。代表的なところとしてはココ

.ttf/.otfとEOTの組み合わせ指定の場合

TrueTypeならびにOpenTypeフォントと、EOTに変換後のフォントが揃ったと仮定する。IE以外で未変換のフォント、IEでEOTフォントを表示させるように指定するには、スタイルシートに以下のように書く。

/*mplus-1c-thin.ttfというファイルを元にする場合*/
/*EOTはmplus-1c-thin.eotというファイル名で作成したと仮定*/
 
/*今回使用するフォントにtestfontという名称をふる*/
@font-face{
	font-family: "testfont";
	src: url("mplus-1c-thin.eot");
}
@font-face{
	font-family: "testfont";
	src: url("mplus-1c-thin.ttf") format("truetype");
}
 
/*h1見出しのフォントを全てtestfontにする場合*/
h1{
font-family: "testfont";
}

あらかじめ@font-faceでfont-familyの名前空間を確保しておき、srcでパスを指定する。その後、実際に使う箇所ではfont-familyで呼び出す。何も難しいところは無い。ただ、IE用のEOT指定の場合はformatをつけてはいけないというところは注意だ。
このように.ttf/.otfと.eotの組み合わせ指定をすれば、4以上のIEを含めた大抵のブラウザで表示が出来る。カバー率は高いだろう。

WOFFを使用する場合

ただ、TrueTypeやOpenTypeをそのまま指定する方法には、閲覧者がフォントをそのままダウンロードできてしまうという短所がある。WEB制作者的にそれで困るところはあまりないが、フォントメーカーなどはそれでは困るので、Webフォント用の新規格としてWOFFというのが策定された。最新ブラウザなどは一様に対応しているし、これから主流になっていくであろうフォーマットだ。
WOFFのメリットとして、著作権情報が盛り込めたり、データを圧縮できたりというものがある。特に後者の恩恵は大きい。

/*mplus-1c-thin.woffというファイルを元にする場合*/
 
/*今回使用するフォントにtestfontという名称をふる*/
@font-face{
	font-family: "testfont";
	src: url("mplus-1c-thin.woff") format("woff");
}
 
/*h1見出しのフォントを全てtestfontにする場合*/
h1{
font-family: "testfont";
}

WOFFに対応するブラウザは、IEは9以降、Chrome、Firefoxは割と前のヴァージョンからに対応しているので意識しないで良いだろう。Safariでは、Mac版、iOS版とも対応は5.1からと遅めで、これはOSX10.6 Snow LeopardとiOS5以降となる。iOS5以降ということで、日本発売のiOSデバイスではiPhone3Gが非対応になるし、iPhone3GSでもiOS5にアップグレードしないユーザは切り捨てだ。WOFFだけ指定するのは、MacやiOSにおいてもIE8以前バッサリ切り捨てと同じくらいの決断となる。
WOFFへの対応ヴァージョンの確認はこちらのページが便利だろう。

Webフォントのサブセット化

何とかWebフォントの容量を軽くしたいという場合には、サイト上で使う文字だけを組み込んだサブセットを作成する方法がある。武蔵システムのサブセットフォントメーカーというソフトを落として使うのが早い。サブセット化&WOFF変換をすれば、日本語フォントでも常識的なサイズに落ちてくれる。

とは言え、日本語Webフォント導入の動機は薄いかも

各社ブラウザの対応によって、確かにWebフォント導入への障壁は低くなっている。ただ、そのわりにWebフォントを使ったサイトというのがイマイチ流行っていないのは、やはり日本語の文字数が膨大で、ページ本文のフォントとして使用するのがあまりにナンセンスだからだろう。例に挙げたmplus-1p-thin.ttfというフォントで1.6MB。フォントによっては20MBくらいになるものもあるので、気に入ったデザインのフォントをよく考えないで採用すると泣きを見る。あくまでせいぜいアルファベット程度の文字セットをダウンロードさせようという目論見から来ている規格なのだろう。


PHP、JavaScriptにおける三項演算子(条件演算子)

ライブラリなどの出来合いコードを読解するようになると、割と目にすることも多い三項演算子(条件演算子とも)。条件分岐のifクローズをただの式に変えてしまうということで、その使用には抵抗がある向きも多いと思う(実際私もあまり好きではない)。ただ、三項演算子イコール単純なifクローズの省略記法というわけではなく、少々のメリットも存在する。その辺りを含めて、PHPとJavaScriptにおける三項演算子の使い方をまとめてみることにした。

三項演算子の基本的使用法

三項演算子については、PHP、JavaScriptともにC言語と同じ”?”や”:”といった演算子を使う。とりあえずPHPの場合で説明すると、

//ifクローズを使った場合
if($age >= 20){
$alcohol = "OK";
} else {
$alcohol = "NG";
}
 
//三項演算子を使った場合
//(式1) ? (式2) : (式3);
//(式1)が真であるとき(式2)、偽であるなら(式3)
$alcohol = $age >= 20 ? "OK" : "NG";

という具合になる。JavaScriptの場合は変数に$がつかない程度の違い。

三項演算子に慣れて親しんでいないと、直感的に理解できず少し混乱してしまう部分もあるかもしれない。上の例で見ると、変数$bへの代入で使われる”=”と、(式1)であるところの”$a <= 100"のような条件式で使われる"<="等の記号が字面上ややこしく見える。 三項演算子は算術演算子のように式を評価した結果を返すと考えなければならない。つまり、

$b = $a * 100;

こういった代入式と同じで、右辺を評価したが代入されているのだ。三項演算子は代入演算子より優先度が高い。

三項演算子に値ではなく式を入れる

ところで、三項演算子を用いた例として、以下のようなものもあり得る。PHPでの例。

//例1
$age >= 20 ? $alcohol = "OK" : $alcohol = "NG";
 
//例2
$age >= 20 ? print "OK" : print "NG";

これは、前回PHPの言語構造を説明した際に出てきた式とは何かというところで、式とは値をもつものという定義により、文法的にパスすると書いた例だ。もちろんJavaScriptの式の定義も同じであり、たとえば

//例1
age >= 20 ? alcohol = "OK" : alcohol = "NG";
 
//例2
age >= 20 ? alert("OK") : alert("NG");

このような式の代入が可能だ。

エルビス演算子(PHP5.3以降のみ)

PHPの場合、三項の真ん中の(式2)を省略して、(式1)がTRUEである場合値も(式1)になる書き方が出来る。この場合、”?:”という演算子を使う。時計回り90度回転したときに誰かさんの顔に見えるので、エルビス演算子と呼ばれる。

//(式1) ?: (式3);
//(式1)が真であるとき(式1)、偽であるなら(式3)
$tobaccoOkFlag = $alcohol == "OK" ?: 0;
 
//つまり、こう書いているのと同じ
$tobaccoOkFlag = $alcohol == "OK" ? 1 : 0;

ただし、PHP5.3以降でないと使えない。

三項演算子の右結合と左結合

三項演算子を多重に用いる場合、評価する順序が問題となってくる。標準的な順序は右結合と呼ばれる、右から評価していく方法。JavaScriptは右結合なので、JavaScriptの例を書く。

//右結合の場合
tobaccoOkFlag = pregnantFlag ? 0 : alcoholOkFlag ? 1 : 0;
 
//評価順序を分かりやすく書くと
tobaccoOkFlag = pregnantFlag ? 0 : ( alcoholOkFlag ? 1 : 0 );

この場合、多重に用いられた三項演算子のうち、右側のものが優先して評価される。alcoholOkFlagが0の場合は、pregnantFlag、つまり妊婦かどうかに関係なくtobaccoOkFlagは0になる。
一方、PHPはこれとは逆の左結合になるのだが、PHPの場合で解釈してみよう。

//左結合の場合
$tobaccoOkFlag = $pregnantFlag ? 0 : $alcoholOkFlag ? 1 : 0;
 
//評価順序を分かりやすく書くと
$tobaccoOkFlag = ( $pregnantFlag ? 0 : $alcoholOkFlag ) ? 1 : 0;

$pregnantFlagつまり妊婦であるかどうかを評価して、妊婦なら0、そうでないなら$alcoholOkFlagの値によって$tobaccoOkFlagが決まる。例があまり直感的なものに出来なかったので、申し訳ない気持ちでいっぱい。

左結合になる言語はほぼPHPのみ。というのも、PHPが左結合になってしまっているのは言語仕様を決めた際の間違いをひきずっているらしい。今更変えられないので、今後もPHPの短所として残り続けるだろう。

右結合の場合のif-elseの書き方

三項演算子が右結合だとif-elseがスッキリ書けるというメリットがある。右結合のJavaScriptの例。

//if-elseを使う場合
if(age >= 65){
message = "高齢者料金になります。";
} else if(age >= 20){
message = "大人料金になります。";
} else if(age >= 6){
message = "小人料金になります。";
} else {
message = "幼児料金になります。";
}
alert(message);
 
//三項演算子を使う場合
message = age >= 65 ? "高齢者料金になります。"
: age >= 20 ? "大人料金になります。"
: age >= 6 ? "小人料金になります。"
: "幼児料金になります。";
alert(message);

さらに、ifクローズが制御構造であるのに対して、三項演算子は式であるため、関数の直接の引数にすることも出来る。

//三項演算子を引数に
alert(age >= 65 ? "高齢者料金になります。"
: age >= 20 ? "大人料金になります。"
: age >= 6 ? "小人料金になります。"
: "幼児料金になります。");

ここまでスッキリするのなら、三項演算子を導入することも検討してよいのではないだろうか。
逆にPHPでは、三項演算子を多重に使用する事は避けた方が良いのかもしれない。