PHP アプリケーションの表示形式と機能を Smarty で分離する

賢いテンプレート・エンジンによって、基礎となる設計ロジックから Web ページ・マークアップを切り離す

Comments

PHP による Web アプリケーションを作り始めるのは簡単なことです。PHP 言語の構文は整理されており、容易に習得できます。PHP は HTML や JavaScript、CSS と直接混在できるため、目に見える結果を即座に得ることができます。また PHP アプリケーションは、独自の Web サーバーや、ホスティング・サービスに容易にデプロイすることができます。

しかし、PHP が他のページ・マークアップと容易に混在できることによって、マイナス面も生じます。PHP コードは、プログラム・ロジックや SQL (Structured Query Language) クエリー、関数、クラス、開発者のコメント、HTML、CSS スタイル、そしてスクリプトが、文字通りクモの巣 (web) のように複雑に絡み合った状態になりがちです。さらに悪いことに、PHP からコンテンツを出力する方法は、echo から出力バッファリングに至るまで、数多くあります。そのように混乱したページを維持管理するのは大仕事です。特に害のない、コードやマークアップの変更でも大騒ぎになり、またページを機能強化しようとすると、デザイナーとプログラマーが一緒に作業しなければなりません。PHP では、表示形式 (ページの表示方法) と機能 (ページの目的と構成) は渾然一体です。

理想的には、表示形式と機能は別々にされています。CSS と HTML の目的は、正にそこにあります。CSS は表示形式であり、HTML は機能です。PHP の場合では、ページ・マークアップとコードが分離されていれば理想的です。コードは入力を処理し、決定を行い、そして表示用のデータを作成する一方、マークアップはデータを想定し、そのデータの情報を描画するために必要な土台を提供します。

例えば、ホームページのマークアップには、ユーザーがログインするための「入力用の空白」や、ユーザーの画像や重要情報のためのプレースホルダー用の領域が用意されているかもしれません。このテンプレート (ページの表示パターンを提供するため、そう呼ばれます) は、完全にデザイナーの担当範囲です。デザイナーはページ全体の見栄えをコントロールし、そして名前や画像、その他のデータ用のプレースホルダーを用意します。一方、コードは単純にプレースホルダー用のデータを提供します。開発者は、ひたすら計算の仕事に集中します。

もちろん、表示形式と機能は協力する必要があります。もしテンプレートがドル金額を想定しているのであれば、コードは URL を提供すべきではありません。もしテンプレートがオブジェクトを想定しているのであれば、コードはリストを提供すべきではありません。つまりテンプレート・システムは表示形式と機能を分離しなければなりませんが、同時に両者の橋渡しをする必要があります。

一般的な Web アプリケーション用のプログラミング言語の大部分 (Perl と Python、Ruby、Java™) はテンプレート・エンジンを持っており、PHP も例外ではありません。検索エンジンに PHP template engine と入力すれば、おそらく 25 種類以上のテンプレート・エンジンが見つかるはずです (調査した各エンジンの強みを強調したリスト、The PHP Template Engine Roundup を「参考文献」にあげてあります。)

一部の PHP テンプレート・エンジンはスピードが最適化されており、また他のテンプレート・エンジンは、表示形式と機能の分離を促進する一方で簡単に使えるように設計されています。一部のパッケージではプレースホルダーが PHP そのもので記述されていますが、他のソリューションではカスタムの省略型プログラミング言語が使われています。どのテンプレート・エンジンを選択するかは要件次第です。そこで、少しばかり調査と実験をしてみましょう。

ここでは、最も一般的な PHP テンプレート・エンジンの 1 つである Smarty を紹介します。Smarty の「コード」には独自の構文と拡張可能な演算子リストがありますが、Smarty システムを学ぶのは難しくありません。Smarty のドキュメンテーションを読むか、あるいはざっと眺め、そのすべての機能に慣れ親しんでください。最初は Smarty を利用した小さな例から始め、そして必要に応じてレパートリーを広げながら熟練者になりましょう。

Smarty について

Smarty の Web サイトには活発なメーリング・リストやサポート・フォーラム、そして IRC (Internet Relay Chat) フォーラムがあります (「参考文献」を参照)。開発は継続的に行われていますが、この記事は 2007年3月7日にリリースされた V2.6.18 を元に書かれています。

Smarty には、PHP の API (application programming interface) と表示エンジンという、2 つの側面があります。アプリケーション・コードは API を呼び出してコード変数をテンプレート・プレースホルダーと関連付け、また表示エンジンは Smarty マークアップを解釈し、ループを実行し、プレースホルダーを参照し、そして最終的な結果を表示します。Smarty には次のような機能が含まれています。

PHP の基本的なすべてのデータ構造を表示する演算子
単純な変数を表示し、配列あるいは連想配列に対して繰り返しを行い、そしてクラスのメンバーを表示します。
プレースホルダーのデフォルト値
PHP コードが変数とプレースホルダーを関連付けしない場合には、代わりにデフォルトが表示されます。
入力されるデータに基づいて表示すべきものを動的に選択できる、ifthenelse などの制御演算子
例えばデザイナーは、口座残高がマイナスの場合には赤の太字で、プラスの場合には黒い文字で表示するように選択するかもしれません。この種の表示ロジックをテンプレートの中で分離することができます (そうすることで作業がずっと楽になります)。
リストや表の作成を単純化する特別な変数を提供する、ループ制御
例えば、ループの最初の繰り返しをテストして表のヘッダーを作ることができます。また、ループを繰り返しながら一連の値を総当たりすることができます。これは表の行の色を 1 行ごとに変えるために最適な方法です。
描画される際にデータを変更するための修飾子
例えば、あるプレースホルダー (例えば $name ) を、Smarty のマークアップ <strong>{$name|upper}</strong> を使って太字の大文字で表示することができます。

<strong> は純粋な HTML です。中括弧 ({} ) は Smarty のマークアップを表し、$name はプレースホルダー、そして |upper は修飾子です。また、Smarty の機能を拡張して独自の修飾子を作成することもできます。
スクリプトと生の PHP コードを含める必要がある場合には、literal 演算子と php 演算子を使うことができます。
literal 演算子内にあるすべてのものは、そのまま最終的なページに渡されます。php 演算子の中に置かれたコードは、あたかも <?php ... ?> エスケープの中に埋め込まれたかのように実行されます。

また、{include ...} 演算子を使って Smarty テンプレートを再利用することもできます。パフォーマンスを高めるために各テンプレートはキャッシュされ、使われるたびに変換する無駄が省かれます。Smarty の Web サイトは、さまざまな話題を網羅したドキュメンテーションと例を提供しています。また Packt Publishing が Smarty の習得と参照に適した『Smarty: PHP Template Programming and Applications』というタイトルの本を出版しています (「参考文献」を参照してください。ただし注意すべき点として、新しい演算子は説明されておらず、他の演算子も記述が正しくありません。これはこの本が以前のバージョンである Smarty V2.x について説明しているためです)。

Smarty を使って開発する

この 1 回の記事では、Smarty のすべての機能を列挙して説明することはできません。しかし、これから示す小さな例であっても、テンプレートの力を十分に示すことができます。

Smarty は、次のように簡単にアプリケーションに追加することができます。

  1. Smarty.zip デモ・コードをダウンロードします (「ダウンロード」を参照)。
  2. パスに Smarty を解凍し、インストールします。
  3. Smarty クラスを要求するアプリケーションを作成します。
  4. アプリケーションと並行する 2 つのディレクトリーを作成します。
    • templates はテンプレートを含みます。
    • templates_c はキャッシュされたテンプレートを含みます。

例えば、サンプル・アプリケーションのフォルダーは Example.class.php index.php templates/ templates_c/ を含んでいます。

templates ディレクトリーの中のファイルは Smarty エンジンによって読み取られます。Web サーバーがこれらのファイルに適切にアクセスできることを確認します。これとは別に、templates_c の内容を読み書きできる必要があります。これはキャッシュされたテンプレートのコピーがここに置かれるためです。少なくとも Web サーバーが templates_c に書き込める必要があります。あるいは、もしセキュリティーの懸念がなければディレクトリーをモード 777 に変更します。

リスト 1 は、PHP V5 クラスの代表である Example.class.php を示しています。リスト 2 は index.php というアプリケーションを含んでいます。ブラウザーでこのサンプル・アプリケーションを表示した結果を図 1 に示します。

リスト 1. 任意の型の名前付きプロパティーの任意のリストを保存する、単純な PHP V5 クラス
<?php

//
// Example is a simple class that stores an arbitrary
// number of named properties.
//

class Example {
    private $catalog = array();

    public function SetProperties( $arrayVariables ) {
        foreach ( $arrayVariables as $name => $value ) {
            $this->SetProperty( $name, $value );
        }
    }

    public function SetProperty( $name, $value ) {
        $this->$name = $value;
        $this->catalog[] = $name;
    }

    public function GetProperties( ) {
        $result = array();
        foreach ( $catalog as $name ) {
            $result[$name] = $this->GetProperty( $name );
        }

        return( $result );
    }

    public function GetProperty( $name ) {
        return ( $this->$name );
    }
}
?>

Example は任意の数の名前付きプロパティーを保持する単純なクラスです。このクラスで最も興味深い部分は 18 行目の $this->$name = $value; です。これによってクラス・インスタンス・メンバーが動的に作成されます。例えば $example->SetProperty( 'name', 'Groucho' ) を呼び出すと、(通常の) $example->name を使って名前を取得することができます。

リスト 2. Web ページ・マークアップを持たない PHP アプリケーション
<?php
require( 'Smarty.class.php' );
require( 'Example.class.php' );

$groucho = new Example();
$groucho->SetProperty( 'name', 'Groucho' );
$groucho->SetProperty( 'quote',
    'Time flies like an arrow. Fruit flies like a banana.'
);

$chico = new Example();
$chico->SetProperties( array(
        'name' => 'Chico',
        'quote' => "There's no such thing as a sanity clause!"
    )
);

$movies = new Example();
$movies->SetProperties( array(
    'movies' => array(
        'A Night at the Opera',
        'Horse Feathers',
        'Coconuts',
        'The Big Store'
    ))
);

$looks = new Example();
$looks->SetProperty( 'novelty', array(
        array( name =>'Groucho', 'signature' => 'moustache' ),
        array( name =>'Chico', 'signature' => 'hat' ),
        array( name => 'Harpo', 'signature' => 'raincoat' ),
        array( name => 'Zeppo', 'signature' => 'hair')
    )
);

$smarty = new Smarty();
$smarty->assign( 'person', $chico );
$smarty->assign( 'people', $looks->novelty );
$smarty->assign( 'quote', $groucho->quote );
$smarty->assign( 'movies', $movies->movies );
$smarty->display( 'template.tpl' );
?>

リスト 2 は、PHP の変数を Smarty のプレースホルダーと関連付けるための一般的な方法を反映しています。$smarty->assign( 'name', $x ) という行は、$x という PHP 変数 (あるいは配列やオブジェクト) をプレースホルダー名と関連付けます。テンプレートの中で name が現れる場所には、必ず $x の値が現れます。

図 1. 表示形式と機能を結合させて最終的なページを描画する
表示形式と機能を結合させて最終的なページを描画する
表示形式と機能を結合させて最終的なページを描画する

Smarty のテンプレートはどんな風に見えるのでしょう。Smarty のコードは非常に軽量です (リスト 3、4、5)。Smarty は、中括弧 ({} ) の中にあるすべてのものを Smarty コードとして扱います。従って、もし何か他のページ・マークアップ (埋め込みの CSS あるいは JavaScript など) が中括弧を使う場合には、そのマークアップを {literal}...{/literal} で囲む必要があります (リスト 3)。

リスト 3. メイン・テンプレートの中に含まれる単純なヘッダー・テンプレート
<html>
<head>
	<title>{$title|default:'The Marx Brothers'}</title>
	<style type="text/css">
	{literal}
	body {
	    font-family: Verdana, Arial, sans-serif;
	    margin: 5%;
	}
	{/literal}
	</style>
</head>
<body>

先ほど触れたように、リスト 3 はマークアップをそのまま描画するために {literal} 演算子を適用しています。この区切り文字の間にあるすべてのテキストは、それ以上解釈されず、そのまま渡されます。3 行目では <title> という名前のプレースホルダーの値を表示しています。このプレースホルダーは PHP アプリケーションのスカラー変数に関連付けられています。|default: 修飾子は、<title> が関連付けされていない場合にデフォルト値を提供します。Smarty 演算子の中の空白に注目してください。これを削除しなければならないことがよくあります。幸い Smarty コンパイラーは、わかりやすいエラー・メッセージを提供してくれます。

リスト 4 はページのフッターですが、描画時に判断を行うための {if condition} の使い方を示しています。ここでは、プレースホルダー quote の値が設定されていると、{if} {/if} の間にあるマークアップが表示されます。{$quote|upper} という行は、quote の値をすべて大文字で出力します。|upper はストリング出力を変更する多くの修飾子の 1 つです。これも、ストリングの内容を表示方法から分離する上で非常に便利です。

リスト 4. フッター・テンプレート
<div style="clear: both;">
    <p align="center">
        {if $quote}
            <{$style|default:'strong'}>
                {$quote|upper}
            </{$style|default:'strong'}>
        {/if}
    </p>
</div>
</body>
</html>

リスト 5 は最終的なページを描画します。

>リスト 5. このアプリケーション用の基本的な Smarty テンプレート
{include file='header.tpl'}

<p>
    {$person->name} said, "{$person->quote}"
</p>

<ul>
    {section name=index loop=$movies}
    <li>
        {$movies[index]}
    </li>
    {/section}
</ul>

<table>
{foreach item=person from=$people name=people}
    {if $smarty.foreach.people.first}
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Signature</th>
        </tr>
    {/if}
    <tr bgcolor="{cycle values="#eeeeee,#d0d0d0}">
        <td>{$smarty.foreach.people.iteration}</td>
        <td>{$person.name}</td>
        <td>{$person.signature}</td>
    </tr>
{foreachelse}
    <tr><td>Print this only if there's no data.</td></tr>
{/foreach}
</table>

{include file='footer.tpl'}

このアプリケーション用の基本的な Smarty テンプレートは、いくつかの Smarty 演算子を使っています。

{include file='filename'}
PHP そのものの include() メソッドとほとんど同じように動作し、filename の内容を即座に挿入して解釈します。ここには示してありませんが、あるテンプレートから別のテンプレートに変数を渡すことができ、それによって再利用を促進することができます。
{$person->GetProperty('name')}
person GetProperty() という名前のメソッドと関連付けられていると仮定すると、{$person->quote} のように、あるオブジェクトのメソッドを呼び出してオブジェクト・メンバーを参照することができます。
{section name=index loop=$placeholder}
配列に対して繰り返しを行います。loop 属性はプレースホルダーに名前を付け、name 属性は配列のインデックスに対して使用する名前を割り当てます。ループの中では配列要素を {$placeholder[index]} として参照します。
foreach
section と同じように繰り返しを行いますが、連想配列の配列 (データベース・クエリーの行のリストなど) を処理するための非常に便利な機能があります。各連想配列は「フラットにされ」、名前付きの item の索引になります。例えばリスト 5 では、person は名前付きの item です。ループが繰り返されるたびに、person には配列 people の連想配列が割り当てられます。それ以降はループのどこでも、連想配列の中の値を、その鍵を使って {$person.signature} のように参照することができます。
foreach の name 属性
HTML タグの id 属性と同じように、foreach の name 属性はループを一意に識別します。この ID を使うと、ループの状態を反映する特別な変数のセットを参照することができます。例えば、そうした特別な変数の 1 つが first です。この変数は、そのループを最初に繰り返す間にのみセットされます。従って、$smarty.foreach.people.first という値は、foreach ループ (foreach) に関連付けられる、people (people) という名前の特別な Smarty 変数 (smarty) を参照します。ご想像の通り、last という値と、1 から始まり繰り返しごとにインクリメントされる iteration という値もあります。(ゼロから始まるカウンターが欲しい場合には iteration の代わりに index を使います。)
cycle
表を作成する場合に非常に便利です。values のリストを提供すると、Smarty はループを繰り返す間にそのすべての値を総当たりします。bgcolor に cycle を追加すると表の行の色が 1 行ごとに変わるため、表が読みやすくなります。
{foreachelse}
繰り返しの対象の配列が空の場合には、その配列の代わりに {foreachelse}...{/foreachelse} の内容が表示されます。

ここまででテンプレートの概略を見てきたので、リスト 2 は簡単に読めるはずです。これは典型的なものですが、リスト 2 は計算を行い、ページの描画作業を Smarty に任せます。$smarty->display('template.tpl') の行はテンプレートを描画します。Smarty の出力をキャプチャーするためには $smarty->fetch('template.tpl') を使います。

Smarty を使ってスマートな作業をしましょう

ここで示した例は不自然なものかもしれませんが、Smarty の強力さと柔軟性、そしてコードからマークアップを分離することがいかに簡単かを示しています。Smarty は、他にもさまざまな機能を持っています。Smarty はおそらく、皆さんが必要とするものをほとんどすべて実装しています。もちろん、テンプレートの出力を Smarty のプレースホルダーの中にキャプチャーすることができます。もちろん、テンプレートをコンパイルする前でもコンパイルした後でもテンプレートをフィルターすることができ、描画出力が表示されたりフェッチされたりする前に出力を操作することもできます。そしてもちろん、Smarty を使えばテンプレートをキャッシュすることもできます。

必要なことは、PHP コードに $smarty->caching = 1; を追加することだけです。キャッシングが有効であれば、display('template.tpl') を呼び出せば通常通りテンプレートが描画され、テンプレートのコピーがキャッシュに保存されます。次回 display('template.tpl') を呼び出すと、キャッシュされたコピーが活用され、テンプレートが再度描画されることはありません。

キャッシュの失効をコントロールでき、手動でキャッシュを空にでき、またキャッシュされたテンプレート・ファイルが保存された後にテンプレート・ファイルが変更されたら、自動的にキャッシュをリフレッシュさせることができます。リスト 2 に似たコードを読みたいと思ったら、皆さんの Web プロジェクトに Smarty を適用してください。


ダウンロード可能なリソース


関連トピック

  • Smarty の Web サイトは Smarty について学ぶための出発点です。
  • Hasin Hader と Joao Prado Maia、Lucian Gheorghe による共著『Smarty: PHP Template Programming and Applications』(2006年 Packt Publishing 刊) を読んでください。
  • 世界の PHP テンプレートエンジンはわかりやすいコメントの付いた、テンプレート・エンジンの完全なリストです。
  • PHP.net は PHP 開発者のためのリソースです。
  • Recommended PHP reading list」を調べてみてください。
  • developerWorks には他にも PHP 関連の記事が豊富に用意されています。
  • IBM developerWorks の PHP project resources を利用して PHP のスキルを磨いてください。
  • PHP でデータベースを使うのであれば、Zend Core for IBM を調べてみてください。これはシームレスでそのまま使用でき、インストールも容易な、IBM DB2 9 をサポートする PHP の開発環境であり実動環境です。
  • developerWorks の Open source ゾーンをご覧ください。オープンソース技術を使った開発や、IBM 製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。
  • Smarty テンプレート・エンジンをダウンロードしてください。
  • 皆さんの次期オープンソース開発プロジェクトを IBM trial software を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
  • IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などのアプリケーション開発ツールやミドルウェア製品を試してみてください。
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=251582
ArticleTitle=PHP アプリケーションの表示形式と機能を Smarty で分離する
publish-date=07172007