EC-CUBEで必要なSmartyの知識
Smartyとは
SmartyはPHPのためのテンプレートエンジンです。具体的に言うと、PHPのプレゼンテーションからアプリケーションのロジックとコンテンツを分離して管理する事を容易にします。
MVCモデル
MVCモデルは、Model、View、Controllerの略で、ユーザーインターフェイス(UI)を持つアプリケーションソフトを実装するためのデザインパターンです。 EC-CUBEにおけるViewを担当するSmartyの主な恩恵・役割は、 * 画面の見た目とアプリケーションのコードをきれいに分離する * PHPが裏方を担当し、Smartyのテンプレートが表面を担当する Viewの担当範囲としては、html, css, JavaScript, そしてSmartyです。Smartyの特徴
Smartyのユニークな特徴の一つは、テンプレートをコンパイルすることです。
つまり、Smartyがテンプレートファイルを読み込み、それをもとにしてPHPスクリプトを作成するということです。
一度作成してしまえば、その後はコンパイルされたPHPスクリプトが実行されるので、各リクエスト時にテンプレートファイルのパースによるオーバーヘッドがありません。
EC-CUBEにおけるコンパイルファイルの置き場
EC-CUBEはSmartyをモジュールとして内包しており、テンプレートファイルである.tplファイルの設置場所をデフォルトでは
data/Smarty/templates
の配下にしています。管理画面用のテンプレートは/adminの下に、それ以外のフロント側の各デバイス別のテンプレートファイルは、/default や /sphone などの下に置かれます。ただし、レスポンシブを採用した作りの場合は、/sphone を使わずに /default に集約するやり方や、/default も使わずに独自のテンプレート名を付けたフォルダを採用することもあります。
Hint&Tips
.tplファイルをコンパイルしたファイルの設置場所は、data/Smarty/templates_c
となります。このディレクトリ配下に、/templates 配下と同じ構造のディレクトリが自動で生成され、その中に xxxxxxxxxxxx.tpl.php のファイル形式でコンパイルされたファイルが置かれます。
ページへのアクセスが発生したタイミングで.tplを元にコンパイルされたファイルが生成され、以後の同ページへのアクセスはこの.tpl.phpファイルが表示に使われます。(.tplを変更せず、.tpl.phpの中身を直接変更すれば、表示も変わります)
Hint&Tips
ローカル環境にMAMPなどでEC-CUBEを構築する場合、Gitリポジトリとしてはこれらのコンパイルされたファイルの置き場であるtemplates_cは含めていないケースが多いです。そして、このディレクトリの配下にコンパイルファイルを生成しようとすると、ディレクトリが見つからないPHPエラーが発生します。
EC-CUBEをローカルに構築する場合、まずこのtemplates_cの存在と、中にディレクトリやファイルを追加できるようにパーミッションを付与することを忘れないようにしてください。
Smartyの情報源
Smartyの情報はあまり多くなくて、日本語で出版されている書籍でもおそらく2冊しかありません。
情報源としては、https://www.smarty.net/ がオススメですが、その中でもテンプレートデザイナのためのSmartyのセクションはフロントエンドよりのテーマに絞ってあるので困った時はまずはこの辺りを読んでみてください。
基本的な文法
PHPから割り当てられた変数
単純な変数のケース
PHP(C)とSmarty(V)の間のデータの流れは一方通行で、PHP側でセットしたデータをSmarty側が読みに行く流れになります。一番基本的な例として、
<?php
$this->hoge = 'ほげ';
?>
という形式でPHPがデータをセットしたとすると、これをSmarty側から、
<div>これは、<span><!--{$hoge}--></span>です。</div>
のように参照すると、
<div>これは、<span>ほげ</span>です。</div>
とHTMLのソースには表示されます。簡単です。
変数が配列のケース
PHPから渡されるデータは、配列であることが多いです。例えば、
<?php
$this->arrFruits[1] = 'りんご';
$this->arrFruits[2] = 'みかん';
$this->arrFruits[3] = 'いちご';
?>
のようにセットした場合、これらはSmarty側から、
<div>これは、<span><!--{$arrFruits[1]}--></span>です。</div>
<div>これは、<span><!--{$arrFruits[2]}--></span>です。</div>
<div>これは、<span><!--{$arrFruits[3]}--></span>です。</div>
のように参照すると、
<div>これは、<span>りんご</span>です。</div>
<div>これは、<span>みかん</span>です。</div>
<div>これは、<span>いちご</span>です。</div>
とHTMLのソースには表示されます。簡単です。
簡単ですけど、同じことを何度も書くのは面倒ですし、そもそもこの配列の中に要素が幾つあるのかが事前に分からないと何行書けばよいのかも分かりません。
それゆえ、配列の中身をアクセスするときは、よほどのことがなければ普通はループ処理を用いてデータの中身を参照します。
Hint&Tips
プログラムは3つの基本処理の組み合わせで書かれるとされています。
すなわち、
1. 順次処理(プログラムは上から下に処理します、という意味)
2. 分岐処理(プログラムは条件に応じてすすむ道が分かれます、という意味)
3. 反復処理(プログラムは条件を満たす限り処理を繰り返します、という意味)
の3つです。
この3つの流れしかないことを理解すればどんな処理でも対処できます。
配列要素への参照方法
ループ処理を説明する前に、Smartyで意外と躓く場所の一つが配列要素への参照方法だと個人的には思います。
配列の要素へのアクセスは、[]で囲って中にkey値を入れる方法に加えて、下記のように「.」でkeyと結合させる方法でもOKです。
<div>これは、<span><!--{$arrFruits.1}--></span>です。</div>
<div>これは、<span><!--{$arrFruits.2}--></span>です。</div>
<div>これは、<span><!--{$arrFruits.3}--></span>です。</div>
しかし、多次元の配列を扱う場合、要素への参照方法に制限がでてきます。多次元配列へのアクセスの場合、[]でkeyを指定するのではなく「.」でアクセスする必要があります。例えば、
<?php
$this->arrAnimal[1]['umi'] = 'さめ';
$this->arrAnimal[2]['riku'] = 'うま';
$this->arrAnimal[3]['sora'] = 'わし';
?>
のという2次元(2x3)配列の場合、
<div>これは、<span><!--{$arrAnimal[1]['umi']}--></span>です。</div>
<div>これは、<span><!--{$arrAnimal[2]['riku']}--></span>です。</div>
<div>これは、<span><!--{$arrAnimal[3]['sora']}--></span>です。</div>
の参照方法だとSmartyの文法エラーになりますが、
<div>これは、<span><!--{$arrAnimal.1.umi}--></span>です。</div>
<div>これは、<span><!--{$arrAnimal.2.riku}--></span>です。</div>
<div>これは、<span><!--{$arrAnimal.3.sora}--></span>です。</div>
の参照方法であれば、
<div>これは、<span>さめ</span>です。</div>
<div>これは、<span>うま</span>です。</div>
<div>これは、<span>わし</span>です。</div>
として表示されます。
多次元配列を扱う場合、ループ処理の中で更にループを回すようなケースも出てくると思いますが、このような時は「.」で参照keyをつなぐか、assign関数にて一度別の変数に値をセットし、その新しい変数値をkeyにして配列要素に参照するなど、多少の経験とテクが必要になると思います。
また、[]と「.」を両方使って、
<!--{$arrForm[$key1].value}-->
のようなアクセス方法も可能です。$arrForm配列の一番上の要素は[]で取りつつ、その下の要素へは「.」で参照する方法です。
併せてこちらも
制御文
1. 反復(ループ)処理
Smartyには、ループを制御する書式が2つあります。sectionとforeachです。
section系
, は、データが格納された**数値添字**配列をループするために使用します。これは、 が1つの連想配列をループするのとは異なります。foreach系
, はデータの配列をループするために使います。 は ループよりもシンプルできれいな構文で、連想配列をループすることもできます。 ループはネスト(入れ子構造:マトリョーシカ!)させることができます。array変数(配列)を渡すと、その要素数で のループ回数が決まります。
配列を渡しつつも別途整数値を渡してループ回数を決めることもできます。
<?php
$this->arrFruits[1] = 'りんご';
$this->arrFruits[2] = 'みかん';
$this->arrFruits[3] = 'いちご';
?>
PHPでこの配列を渡してきた時、Smarty側でループ処理を用いて配列の要素にアクセスしてみます。
<!--{section name="data" loop=$arrFruits}-->
<!--{assign var=key value="`$smarty.section.data.iteration`"}-->
<div>これは、<span><!--{$arrFruits[$key]}--></span>です。</div>
<!--{/section}-->
この結果は、
<div>これは、<span>りんご</span>です。</div>
<div>これは、<span>みかん</span>です。</div>
<div>これは、<span>いちご</span>です。</div>
のようになります。
同様に、foreachを用いて、配列の要素にアクセスしてみます。比較が面白いので、参考までに幾つかの属性値と呼び方を併記してみます。
<!--{foreach name="fruit" from=$arrFruits item=arrHoge key=fruit_index}-->
index is <!--{$smarty.foreach.fruit.index}-->
iteration is <!--{$smarty.foreach.fruit.iteration}-->
<div id="a">これは、<span><!--{$arrFruits[$smarty.foreach.fruit.iteration]}--></span>です。</div>
<div id="b">これは、<span><!--{$arrFruits[$fruit_index]}--></span>です。</div>
<div id="c">これは、<span><!--{$arrHoge}--></span>です。</div>
<!--{/foreach}-->
この結果は、
index is 0
iteration is 1
<div id="a">これは、<span>りんご</span>です。</div>
<div id="b">これは、<span>りんご</span>です。</div>
<div id="c">これは、<span>りんご</span>です。</div>
index is 1
iteration is 2
<div id="a">これは、<span>みかん</span>です。</div>
<div id="b">これは、<span>みかん</span>です。</div>
<div id="c">これは、<span>みかん</span>です。</div>
index is 2
iteration is 3
<div id="a">これは、<span>いちご</span>です。</div>
<div id="b">これは、<span>いちご</span>です。</div>
<div id="c">これは、<span>いちご</span>です。</div>
のようになりました。
さっきの多次元配列の方も、foreachで開いてみます。
<!--{foreach name="animal" from=$arrAnimal item=arrFuga key=animal_index}-->
index is <!--{$smarty.foreach.animal.index}-->
iteration is <!--{$smarty.foreach.animal.iteration}-->
<div id="a">これは、<span><!--{$arrAnimal[$smarty.foreach.animal.iteration]}--></span>です。</div>
<div id="b">これは、<span><!--{$arrAnimal[$animal_index]}--></span>です。</div>
<div id="c">これは、<span><!--{$arrFuga}--></span>です。</div>
<!--{/foreach}-->
この結果は、
index is 0
iteration is 1
<div id="a">これは、<span>Array</span>です。</div>
<div id="b">これは、<span>Array</span>です。</div>
<div id="c">これは、<span>Array</span>です。</div>
index is 1
iteration is 2
<div id="a">これは、<span>Array</span>です。</div>
<div id="b">これは、<span>Array</span>です。</div>
<div id="c">これは、<span>Array</span>です。</div>
index is 2
iteration is 3
<div id="a">これは、<span>Array</span>です。</div>
<div id="b">これは、<span>Array</span>です。</div>
<div id="c">これは、<span>Array</span>です。</div>
になりました。2次元の奥行きを持つ配列を1次元の配列と同じ呼び出し方で参照すると、当然値をArrayとして表示せざるを得ません。なので、
<!--{foreach name="animal" from=$arrAnimal item=arrFuga key=animal_index}-->
<!--{foreach name="animal2" from=$arrFuga item=arrFuga2 key=animal_index2}-->
<div id="d">これは、<span><!--{$arrFuga2}--></span>です。</div>
<!--{/foreach}-->
<!--{/foreach}-->
のように記述を変更してみると、この結果は、
<div id="d">これは、<span>さめ</span>です。</div>
<div id="d">これは、<span>うま</span>です。</div>
<div id="d">これは、<span>わし</span>です。</div>
となりました。
多次元配列のデータを取得することができるようになれば、もうEC-CUBEにおけるSmarty関連の必要なスキルの半分くらいはマスターしたことになると思います。簡単です。
2. 分岐処理
条件に応じて処理を変えるための制御文は、ifを用います。switch文はありませんので、if一択です。簡単です。
if文
ifの構文は公式サイトを見ていただくとして、ifで判定する中身では、下記のようにPHPの関数がそのまま使えるケースもあります。例としては、$dataが配列か否かを判定する関数であるis_array()などもSmartyで使用可能です。
<!--{if is_array($data)}-->
// 処理
<!--{/if}-->
また、デザインの目線でみると複数個ある要素の偶奇判定は意外とありがたいかもしれません。
<!--{if $data is even}-->
// 処理(値が偶数)
<!--{/if}-->
<!--{if $data is odd}-->
// 処理(値が奇数)
<!--{/if}-->
これにより、<li>タグの奇数個目と偶数個目とで色を変えたい場合、even/oddによりclassの値を変えてスタイルに反映させるような使い方もできます。
基本的なテクニック
リクエスト変数への参照
$_GET や $_SESSION などのようなリクエスト変数は、予約済の変数 $smarty の値で取得します。
// GETデータへの参照
<!--{$smarty.get.xxxx}-->
// POSTデータへの参照
<!--{$smarty.post.xxxx}-->
// COOKIEデータへの参照
<!--{$smarty.cookies.xxxx}-->
// SERVERデータへの参照
<!--{$smarty.server.SERVER_NAME}-->
// SESSIONデータへの参照
<!--{$smarty.session.xxxx}-->
定数への参照
EC-CUBEで定義された定数も含めた定数へのアクセスは、下記の書式で参照できます。
// 定数への参照
<!--{$smarty.const.xxxx}-->
例えば、ROOT_URLPATH というPHPで定義された定数を参照する場合は、
<!--{$smarty.const.ROOT_URLPATH}-->
という書式になります。
変数の修飾子
変数に対して、「修飾子」と呼ばれる関数を適用させることで値を加工することができます。
修飾子は加工したいデータに対してパイプ「|」記号を用いて右から左に適用します。
リンク先の修飾子のリストはSmartyで定義されたものをあげていますが、それらに加えて、すべてのPHP関数は、暗黙的に修飾子として使用できます。
例えば、number_format を用いて、数値データに桁区切りのカンマ(,)を挿入することも可能です。
<dd class="txt1"><!--{$total_point|number_format}-->ポイント</dd>
修飾子の連結
Smartyの変数には複数の修飾子を適用できます。それらは左から右に連結された順に適用されます。各修飾子は、| (パイプ) キャラクタで連結しなければなりません。
外部ファイルの読み込み
外部のファイルを読み込むことで、共通コンテンツを複数のファイルで使い回すことができます。
<!--{include file='page_header.tpl'}-->
このことは、.tplを共用する機能ですが、例えばフロント側の全ページで共通で使われるCSSやJSを一つの.tplファイルに記載し、<head></head>内でincludeすることで同じ読み込みの記述を簡略化することも可能です。
<!--{include_php file=test.php}-->
のような記述でPHPをincludeする機能もありますが、非推奨となっていますので、使わない方がよいと思います。
カスタム関数の利用
<select>タグの中身を動的に生成する関数です。
PHP側で
<?php
$this->arrJob[1] = 'エンジニア';
$this->arrJob[2] = '弁護士';
$this->arrJob[3] = '教師';
?>
のようにデータが設定してある場合、
<!--{assign var=key1 value="order_job"}-->
<select name="<!--{$key1}-->">
<option value="" selected="selected">選択してください</option>
<!--{html_options options=$arrJob selected=$arrForm[$key1].value}-->
</select>
のように記述すると、コンパイルされたHTMLでは
<select name="order_job">
<option value="" selected="selected">選択してください</option>
<option label="エンジニア" value="1">エンジニア</option>
<option label="弁護士" value="2">弁護士</option>
<option label="教師" value="3">教師</option>
</select>
のように自動で出力されます。<select>メニューで何かを選択してこのページに戻ってきた場合に自動でselected属性が<option>タグに付与される機能も持っています。
チェックボックスを動的に生成する関数です。
<!--{assign var=key value=search_sex}-->
<!--{html_checkboxes name="$key" options=$arrSex selected=$arrForm[$key]}-->
ラジオボタンを動的に生成する関数です。
<!--{assign var=key1 value="order_sex"}-->
<!--{html_radios name=$key1 options=$arrSex selected=$arrForm[$key1].value}-->
PHP側で配列で渡されたデータをSmarty側でhtmlの<table>タグに変換する関数です。
$this->data = array(1,2,3,4,5,6,7,8,9);
PHP側で1次元配列を用意して、
<!--{html_table loop=$data}-->
Smarty側でこんな感じで受け取ると、
<table border="1">
<tbody>
<tr><td>1</td><td>2</td><td>3</td></tr>
<tr><td>4</td><td>5</td><td>6</td></tr>
<tr><td>7</td><td>8</td><td>9</td></tr>
</tbody>
</table>
になります。行と列を何も指定しなくても、勝手に最適な表にしてくれるようです。
列数を指定したい場合は、
<!--{html_table loop=$data cols=4 table_attr='border="0"'}-->
のようにします。
1行目をthにして項目名にしたい場合は、
<!--{html_table loop=$data cols="first,second,third,fourth" tr_attr=$tr}-->
のように cols="first,second,third,fourth" として項目行を設定できます。
$this->data = array(1,2,3,4,5,6,7,8,9);
$this->tr = array('bgcolor="#eeeeee"','bgcolor="#dddddd"');
PHP側で背景色用にカラーコードを持たせた配列を用意した例として上記を設定すると、この結果生成される<table>タグの中身は、
<table border="1">
<thead>
<tr>
<th>first</th><th>second</th><th>third</th><th>fourth</th>
</tr>
</thead>
<tbody>
<tr bgcolor="#eeeeee"><td>1</td><td>2</td><td>3</td><td>4</td></tr>
<tr bgcolor="#dddddd"><td>5</td><td>6</td><td>7</td><td>8</td></tr>
<tr bgcolor="#eeeeee"><td>9</td><td> </td><td> </td><td> </td></tr>
</tbody>
</table>
となります。
高度なテクニック
読み込みテンプレートへのパラメータ渡し
<!--{include file=test.tpl hoge="hogehoge" piyo="piyoyo"}-->
のような書式で、属性値としてtest.tplに対してhoge=hogehogeとpiyo=piyoyoを渡すこともできます。あまり使わない書式ですが、例えばforeachでループを回し、その値をincludeするテンプレートに渡す時などに役立つかもしれません。
ダブルクォート内に埋め込まれた変数
Smartyタグの中で属性値としてSmarty変数($から始まる文字列)を扱うには、ダブルクォートで囲まれた書式にする必要があります。
<!--{include file="subdir/$tpl_name.tpl"}-->
の書式であれば、$tpl_nameはSmarty変数として扱われ、値が置き変わりますが、
<!--{include file='subdir/$tpl_name.tpl'}-->
の書式だと、$tpl_nameは置き替わらず、文字通り$tpl_name.tplとして表示されます。
また、「.」(ドット)や配列の参照([])を含む場合は、その変数を「`」(バッククォート) で囲む必要があります。
<!--{include file="`$module.contact`.tpl"}-->
カスタム関数の作成
どうしてもSmartyの機能を駆使しても動的な処理ができない場合、独自のカスタム関数をPHP側で作り、Smartyに実装することも可能ですが、ここまでするケースはほとんどありません。(一応、知識として)
デバッグ
<!--{$array|@debug_print_var}-->
debug_print_varはデフォルトでは40文字で表示が切られてしまいます。
これはオプションで調節できます。例えば100文字まで表示する場合は、以下のように指定します。
<!--{$array|@debug_print_var:0:100}-->
var_dump的な表示
<!--{$array|@var_dump}-->
PHP側から渡されている全パラメータの確認方法
下記のコードをSmartyに埋め込むと、PHPから渡されている全てのパラメータを表示することができます。
これにより、Smarty側で参照可能な変数を確認することができます。
EC-CUBEにおいては、PLP・PDPでは、$arrProductの配列に商品データを格納してSmartyに渡していますので、まずは定番の$arrProductを調べてみて、それ以外のデータに参照したい時は、下記の方法で定義済み変数を調べてみるとよいでしょう。
<pre>
<!--{php}-->
print_r(get_defined_vars());
<!--{/php}-->
</pre>
おわり
以上で、EC-CUBEで必要なSmartyの知識を終わります。
Smarty自身はもっと高機能なテンプレートエンジンですが、EC-CUBEのフロント側のコーディングで触れるという観点では、上記の内容をマスターすれば十分かなと思います。
Hint&Tips
- Smartyのコンパイルは一度だけ行われるので、テンプレートのパースによるオーバーヘッドが無い
- 再コンパイルは変更があったテンプレートファイルのみで行う
→ .tplファイルの日付を見ているので、新しい日付から古い日付に戻した場合は再コンパイルが行われない
【例】
- hogehoge.tpl を作成(更新日時;2019/04/01 11:00:00)
- hogehoge.tpl を複製し、hogehoge_bk.tplと命名。hogehoge.tplを修正
hogehoge_bk.tpl(更新日時;2019/04/01 11:00:00)
hogehoge.tpl(更新日時;2019/04/01 12:00:00) - hogehoge.tplを破棄し、hogehoge_bk.tplをhogehoge.tplにリネーム
hogehoge.tpl(更新日時;2019/04/01 11:00:00)
このケースだと、hogehoge.tplのもっとも進んだ日時は2019/04/01 12:00:00として認識されているので、2019/04/01 11:00:00のタイムスタンプのhogehoge.tplは最新として扱われず再コンパイルされない
・再コンパイルをするためには
data/Smarty/templates_c/xxxxxxxx/配下にある「%%xxxxxxxxx%%hogehoge.tpl.php」ファイルを削除する