CakePHPで私がElementsを多用する理由は、同じ(もしくは同じような)レイアウトが必要になる場面で同じ(ような)コードをいくつも増やしたくないため

CakePHPをそれほど長く使っているわけではないが、使うたびにチョコチョコと使い方が分かってくる。使い始めた当初はエレメント(APP/View/Elements)の使い方もよくわからなかったが、ぼちぼち使い始めて効果を味わいながら、ここ最近になっていろいろと便利な「レイアウトパーツ」としての価値を考え始めた。もっとも、やりすぎるとオーバヘッドになるのでほどほどにしたほうがいいだろうけれど。


ここでは、ある飲食店のメニューについて考えてみたい。
メニュー(商品名と価格の一覧)は、基本的にその店で提供する飲食の全リストとなる。特にウェブサイトなどの場合では、これらはメニューテーブルもしくはプライステーブルのような形で保持している。

ところで、商品の中にはモーニング、ランチ、ディナーとそれぞれで提供するサービスが異なる場合もあるが、メニューとしての見栄え、つまりレイアウトは全体統一のために変えたくないというのはデザインの考え方として(私はデザイン素人だけれど)一般的だと思う。
そうなると、デザインを修正する際に、全メニュー・モーニングメニュー・ランチメニュー・ディナーメニューとそれぞれに同じ(あるいは同じような)レイアウトが散らばってしまうと、各メニューページを修正する必要がある。最大で4ページ分の修正を余儀なくされるわけだ。そんなコストはとてもじゃないけどかけたくない。

であれば、メニューのデザインは単一のエレメントに閉じ込め、4ページはそれを使い分けるようにする。そうすることで、修正箇所は1つに絞り込める。
ということで、こんなサンプルを作ってみる。

メニュー関連のページは4ページ。さきに述べたように、単純に「全メニュー」「モーニングメニュー」「ランチメニュー」「ディナーメニュー」という独立した4ページが存在するとする。
またこのサンプルではデータベースの呼び出しの変わりに配列を使用している。その理由は単にテーブルを作るのが手間だったから……というのは33%ぐらいあるけれど、データ構造がコードで見えるためなのが33%、方法論の説明なのでどちらでも説明上影響がないことが33%、きまぐれ1%というだけである。
ただしControllerでのデータの呼び出しはModel::find()を使っている”ように見える”ようにコードを書いたので、多少の応用はしやすいと信じている。

また、エラー処理は含めていない。

ではAPP/Controller/TestsController.phpから。

[php]
<?php
App::uses("AppController", "Controller");
class TestsController extends AppController {
private $pricelist;
public function beforeFilter()
{
parent::beforeFilter();
$this->autoLayout = false;
$this->pricelist = array(
array(
‘type’ => ‘morning’,
‘name’ => ‘エッグサンド(コーヒー付)’,
‘price’ => 600,
),
array(
‘type’ => ‘morning’,
‘name’ => ‘ハムトースト(コーヒー付)’,
‘price’ => 700,
),
array(
‘type’ => ‘morning’,
‘name’ => ‘プレーントースト(コーヒー付)’,
‘price’ => 400,
),

array(
‘type’ => ‘lunch’,
‘name’ => ‘明太子パスタ(サラダ・コーヒー付)’,
‘price’ => 850,
),
array(
‘type’ => ‘lunch’,
‘name’ => ‘チキンプレート(サラダ・コーヒー付)’,
‘price’ => 950,
),
array(
‘type’ => ‘lunch’,
‘name’ => ‘ビーフカレー(サラダ・コーヒー付)’,
‘price’ => 900,
),

array(
‘type’ => ‘dinner’,
‘name’ => ‘マダイのローストと温野菜’,
‘price’ => 2200,
),
array(
‘type’ => ‘dinner’,
‘name’ => ‘特選和牛ステーキ130g’,
‘price’ => 3500,
),
array(
‘type’ => ‘dinner’,
‘name’ => ‘スペシャル五目ピザエスニック風’,
‘price’ => 2700,
)
);
}

private function find($type = NULL)
{
unset($ret);
foreach ($this->pricelist as $price) {
if ($type && $price[‘type’] != $type)
continue;
$ret[] = $price;
}

return $ret;
}

public function pricelist()
{
$p = $this->find();
$this->set(‘pricelist’, $p);
}

public function morning()
{
$p = $this->find(‘morning’);
$this->set(‘pricelist’, $p);
}

public function lunch()
{
$p = $this->find(‘lunch’);
$this->set(‘pricelist’, $p);
}

public function dinner()
{
$p = $this->find(‘dinner’);
$this->set(‘pricelist’, $p);
}
}
?>
[/php]

5行目の$pricelistはメニューを擁する配列である。各アクションではこれを直接参照せず、$this->find()という形で必要なデータを抽出している。
10~59行目の膨大な多重配列がメニューデータである。サービス内容は架空である。$this->pricelist[0][‘type’]は’morning’、$this->pricelist[0][‘name’]は’エッグサンド(コーヒー付)’、$this->pricelist[0][‘price’]は600、となる。

61行目からのfind()は、各アクションでデータを取り出すときに呼び出されるメソッドである。$this->find()は省略可能な引数を1つ持っていて、ここに、たとえば’morning’を入れれば$this->pricelist[][‘type’]が’morning’に一致するものだけピックアップする、などのフィルタ処理ができる。Model::find()の場合では$this->find(‘all’, array(‘conditions’ => array(‘type’ => ‘morning’)))のように呼び出すことになるだろう。

73~95行目は各アクションとなる。たとえば「http://www.example.co.jp/tests/pricelist」とアクセスすれば、73行目のアクションが実行され、全メニューが表示される、などのように機能する(ようにコーディングすればいい)。

TestsController.phpについては、これ以上の解説は不要だろう。

ビューの説明をする前に、呼び出される側となるエレメントの説明を先にしておきたい。
エレメントはAPP/View/Elements/pricelist.ctpファイルに収めている。これは、この後説明する4つのビューから呼び出されるものだ。つまり、共通のレイアウトをこのエレメント1つが担っていることになる。

[php]
<?php
echo ‘<h2>’ . $type . ‘</h2>’;
echo ‘<table border=1>’;
foreach ($pricelist as $price) {
echo ‘<tr><th style="text-align:right;">’ . $price[‘name’] . ‘</th><td>’ . $price[‘price’] . ‘円</td></tr>’;
}
echo ‘</table>’;
?>
[/php]

ここで注意するのは、単にコントローラから渡される$pricelistという配列をそのままforeachで回して表示しているだけの内容だが、さらに1つ$typeという見出しを受け取り、それを表示している点だ。

サンプルのレイアウトやデザインは非常にシンプルなものであるが、これが複雑なものだったら……。たとえばエレメントにこれを配置せず、すべてビューに配置してしまった場合、修正が発生したら4つのビューをいちいち直さなければならない。4つなら楽だろう。しかしこれが10だったら、50だったら、100だったら……。

ということで、エレメントは可能な限り細分化する。なるべく単機能化する。プログラミングのモジュール化に近いイメージと言えるだろう。

最後にビューファイル。アクションにあわせて以下の4つ用意している。

APP/View/Tests/pricelist.ctp
APP/View/Tests/morning.ctp
APP/View/Tests/lunch.ctp
APP/View/Tests/dinner.ctp

以下、それぞれ順に掲載する。ビューのコードは短い。それはレイアウトの中心をエレメント側に託しているからだ。

APP/View/Tests/pricelist.ctp
[php]
<?php
echo ‘<h1>メニュー</h1>’;
echo ‘<p>当店のお料理は、どれもお得なプライスでご提供しています。</p>’;
echo $this->element(‘pricelist’, array(‘type’ => ‘全メニュー’));
?>
[/php]

APP/View/Tests/morning.ctp
[php]
<?php
echo ‘<h1>しっかりモーニング</h1>’;
echo ‘<p>7:00~11:00は、モーニングタイムです!</p>’;
echo $this->element(‘pricelist’, array(‘type’ => ‘モーニングメニュー’));
?>
[/php]

APP/View/Tests/lunch.ctp
[php]
<?php
echo ‘<h1>お得なランチ</h1>’;
echo ‘<p>11:00~16:00は、お得なランチタイムです!</p>’;
echo $this->element(‘pricelist’, array(‘type’ => ‘ランチメニュー’));
?>
[/php]

APP/View/Tests/dinner.ctp
[php]
<?php
echo ‘<h1>エグゼクティブディナー</h1>’;
echo ‘<p>16:00~23:00(L.O. 22:30)は、大人のディナータイムです!</p>’;
echo $this->element(‘pricelist’, array(‘type’ => ‘ディナーメニュー’));
?>
[/php]

4つとも、ほとんど同じである。見出しと説明の文言だけが異なり、4行目で必ず呼び出されるのが$this->element(‘pricelist’)である。
ただし、それぞれのパラメータは異なる。持たせるパラメータは、メニューのサブタイトルだ。さきのエレメントの$typeで受け取り、見出しとして表示するようになっている。

ここで紹介しているエレメントの例を見ると「単に普通のエレメントの使い方」でしかないのだが、実はこうした場面は、ウェブ制作の場面では結構多数ある。
ある種のデザインやレイアウトの使いまわしというのは、実は割と細かく細かく存在していて、クライアントからの修正要求があるたびに関連ページをすべて修正するような状況に追い込まれることは、実は割と経験している人が多い気がする。

エレメントを使うことで、デザインやパーツをかなり細切れにすることができる。サブタイトルや画像出力などや、たとえばメニューやリストなど同じものを別のページでも内蔵しなければならない(つまり、単純に特定のページへのリンクを出すだけでは済まず、まったく同じ内容を別のページでも収容しなければならない)場面は意外に発生するものである。iframeで当該ページを埋め込もうか? いやいや、そんなえげつないこと、考えたくもないだろう。

そうした場合には、エレメントは大変有力である。MVCの特徴の一つである、デザインをプログラミングに埋め込むという特徴が上手に活用できるのは、まさにこのエレメントなのではないだろうか。デザインのモジュール化と再利用である。

これをレイアウト(APP/View/Layout)で吸収し、コントローラ側でレイアウトを指定するという方法もあるだろう。実際これをやっていた時期もある。
しかしこれをしてしまうと、大枠の変更をしようとしたときに複数のレイアウトファイルに手を入れなければならなくなることがあるので、レイアウトは可能な限り少なくし(理想は本当に全体で必要となるヘッダや骨子となるタグのみで構成する。私の理想はレイアウト1つのみ)、共通パーツは可能な限りエレメントで吸収するという方法がいいだろう、というのが、私の短いCakePHP経験で言えることのひとつではある。

だから私は可能な限りデザインを細分化し、エレメントによってモジュール化して、パズルのように組み上げていくようにしている。
むろんオーバヘッドはある程度考える必要があるので、エレメントの必要以上のネストや、細かすぎるエレメント化には気を付けるようにはしている。

はじめてのCakePHP (I・O BOOKS)世界の超人気ビール12本セット 【12本詰め合わせ】CakePHP2 実践入門 (WEB+DB PRESS plus)詳解CakePHP辞典―2.0/2.1/2.2/2.3対応Webアプリ開発を加速する CakePHP2定番レシピ119オープンソース徹底活用 CakePHP 2.1によるWebアプリケーション開発

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください