add to hatena hatena.comment (9) add to del.icio.us (0) add to livedoor.clip (1) add to Yahoo!Bookmark (0) Total: 10

CakePHP 動的なウィジェットの作り方

CakePHPではコントローラーでの処理はビューを介してレイアウトのメインコンテンツ($content_for_layout)の部分に展開されます。
その際レイアウトに配置してあるメインコンテンツ以外の部分は基本的にはコントローラーから操作ができません。(たぶん)
一般的なサイトでは2段組みや3段組みのレイアウトを使うケースが多いと思いますが、メインコンテンツ領域以外もCakeを使って柔軟に制御する方法を検討します。
(出力されるHTMLの部品をウィジェットとします)

※今回の実装サンプルはこちらから確認できます。
※ソースコードはこちらからダウンロードできます。
※サンプルではメインコンテンツ側で指定されたカテゴリの情報をフッター部に表示しています。

実現したい機能


・レイアウトは1枚で管理できること(デザインの保守性の為)
・コントローラーやビューからウィジェットの表示・非表示・表示内容を変更できること
・ウィジェットのロジックやビューはメインのコントローラーとは分離して管理できること(汎用的に利用できること)

開発方針

静的なウィジェットであれば、単にelement化してレイアウトから呼び出せばよいだけです。
動的なウィジェットの場合は通常だと、

 ・レイアウト内のelementの中でrequestAction(コントローラー/アクション)を実行する
 ・コントローラー内でrequestAction()を行った結果をビュー経由でレイアウト内の変数にアサインする

といった方法になると思います。

ただrequestAction()を使う場合は、以下のようなデメリットがあります。

 1)パフォーマンスが悪い(らしい)
 2)部品だけの出力を通常のURLから起動できてしまう
 3)パラメータがURL形式のみなので複雑な制御が難しい
 4)メインのコントローラー側からプロパティなどの操作ができない

そこで、今回はコンポーネント(WidgetComponentとする)を利用し、コンポーネント側でミニMVC形式での開発ができるように実装しました。

WidgetComponentの機能

このコンポーネントは単体では何も実行しません。
このコンポーネントを継承して独自のWidgetComponentを作りこむことになります。
(このコンポーネントは継承後に共通利用できるメソッドを格納しています)
共通の処理として、以下のようなことを行っています。

 ・表示、非表示の状態を保持
 ・ウィジェット用ビューのレンダリング
 ・自身のクラスをメインのビューにアサイン

実装準備

サンプルで使ったWidgetCookingComponentを例として解説します。
このウィジェットは指定されたカテゴリのデータを表示するだけの簡単な仕様です。

[コンポーネントの配置]
WidgetComponentとWidgetCookingComponentをコンポーネントディレクトリに配置します。
※ソースコードはこちらからダウンロードできます。

PHP:
  1. /app/controllers/components/widget.php
  2. /app/controllers/components/widget_cooking.php

[ウィジェット用ビューの配置]
ウィジェット用のビューは階層を下げて widgets/ウィジェット名 のディレクトリに配置します。

PHP:
  1. /app/views/widgets/cooking/category.ctp    結果表示用
  2. /app/views/widgets/cooking/category_none.ctp    カテゴリ無し用

[コントローラーからコンポーネントを呼出す]
コントローラー内でWidgetComponentとWidgetCookingComponentを呼び出します。
(WidgetCookingだけでは継承時にエラーになります)

PHP:
  1. var $components = array('Widget','WidgetCooking');

※一般的にはレイアウトは共通化されるので、app_controllerに記述するケースが多くなるはずです。

[レイアウト内に表示メソッドを記述]
今回はWidgetCookingのshowData()メソッドを実行します。

PHP:
  1. <div id="footer">
  2.     <?php /*** ウィジェットの表示 ***/ ?>
  3.     <?php $widgetCooking->showData(); ?>
  4.     :
  5. </div>

ソースコード:WidgetCookingComponent
/app/controllers/components/widget_cooking.php

WidgetCookingComponentはWidgetComponentを継承する形で自由にカスタマイズして構築します。通常のコントローラーと異なる部分は、

 ・モデルはClassRegistryを使って呼び出す
 ・ビューの表示はrender()メソッドで明示的に行う
 ・$this->isDisplay(__FUNCTION__) を使って表示制御を行う

くらいです。

PHP:
  1. <?php
  2. class WidgetCookingComponent extends WidgetComponent{
  3.    
  4.     var $M; // モデルのインスタンス
  5.     var $category_id = null; // 抽出用のカテゴリID
  6.    
  7.     function startup(&$controller) {
  8.         parent::startup($controller);
  9.         $this->M = ClassRegistry::init('MyCooking');
  10.     }
  11.    
  12.     #########################################################################
  13.     /**
  14.      *  カテゴリのセット(メインコントローラー側からも制御できるようにする)
  15.      */
  16.     #########################################################################
  17.     function setCategory($category_id = NULL) {
  18.         if(!is_null($category_id)){
  19.             $this->category_id = (int)$category_id;
  20.         }
  21.     }
  22.    
  23.     #########################################################################
  24.     /**
  25.      *  ウィジェットの表示メソッド
  26.      */
  27.     #########################################################################
  28.     function showData($category_id = NULL, $limit = 5) {
  29.         if(!$this->isDisplay(__FUNCTION__)) return;
  30.        
  31.         $data = array();
  32.         // Viewからカテゴリを指定された場合
  33.         $this->setCategory($category_id);
  34.        
  35.         // データの取得ロジック
  36.         if($this->category_id){
  37.             $M =& $this->M;
  38.             $data = $M->find('all', array(
  39.                     'conditions' => array($M->name.'.category_id' => (int)$this->category_id),
  40.                     'order' => $M->name.'.id',
  41.                     'limit' => $limit,
  42.             ));
  43.         }
  44.         // 該当0件やカテゴリの指定が無い場合にメッセージを表示
  45.         if(empty($data)){
  46.             $this->render('category_none');
  47.             return;
  48.         }
  49.         // 一覧表示
  50.         $this->set('data', $data);
  51.         $this->render('category'); // views/widgets/category.ctp が呼ばれる
  52.     }
  53. }

ソースコード:WidgetCookingComponentのビュー
/app/views/widgets/cooking/category.ctp

PHP:
  1. <div style="margin:5px; padding:5px; background-color:white; color:black; text-align:left">
  2. <p><strong>料理メニューのウィジェット表示</strong></p>
  3. <?php foreach($data as $i => $_): ?>
  4.     <p><?php echo $_['MyCooking']['name']?></p>
  5. <?php endforeach; ?>
  6. </div>

コントローラーからウィジェットを制御する

今回のウィジェットはsetCategory()メソッドに渡されたカテゴリ内容を表示する仕様にしていますので、コントローラーから

PHP:
  1. // カテゴリが1のデータをウィジェットに表示
  2.     $this->WidgetCooking->setCategory(1);

のように設定します。
ウィジェットを非表示にしたい場合は、

PHP:
  1. // ウィジェットのshowDataメソッドを非表示にする
  2.     $this->WidgetCooking->display('showData', false);

ウィジェットに表示メソッドが複数あり、全てを非表示にしたい場合は、

PHP:
  1. // ウィジェットの全メソッドを非表示にする
  2.     $this->WidgetCooking->displayAll(false);

とします。

まとめ

ウィジェットにもビューキャッシュをつけたいなどの課題はありますが、以下のようなケースに利用できると思います。
 ・メインのコントローラーと連動したレコメンド表示
 ・動的なサイドメニュー表示の切り替え
 ・認証状態やカートのようにセッションと連動したウィジェット表示

関連するその他の記事

Comments

Leave a Reply