FuelPHPのViewModelがPresenterに進化を遂げていた
久々にFuelPHPを触ったら、
1.7系まであったViewModelが1.8以降Presenterなるものに
進化を遂げていたので備忘。
WebアプリケーションフレームワークではMVCモデルをベースとして
よく設計されていると思うのですが(自分はPHPで実際触ったFWはFuelしかないけども)、
FuelPHPはMVCモデルの概念に付け加えて
さらにViewModelというものがあります。(ありました。)
ViewModelとは
一般的なMVCモデルだと、
- Controller クライアントから受け取ったRequestを基に、Modelに処理を依頼する。Modelから返却された処理結果データをViewにわたす。
- Model Controllerからの情報を基に処理のロジックを組み立てる&データアクセス(DB等)を行い、処理結果をControllerへ返却する。
- View Controllerからの情報を画面表示(HTML出力処理)を行う。
という役割をそれぞれが果たしているというのが自分の解釈ですが、
MVC + ViewModel が加わったFuelのアーキテクチャーはこうなります。
各々のモジュールの役割はMVCとほぼ変わりはありませんが、
Controller←→View間に ViewModelが介すると
- Modelを通してControllerが受け取った値をさらに整形し、画面出力しやすい形でViewにわたす
このような役割を担ってくれます。つまり
- Controllerは受け取った値を一切触ることなくそのままViewModelに渡すだけになり、Controllerの処理がスッキリまとまる
こういうメリットがあります。
いやいや別にあろうがなかろうがそんな変わんないでしょ、と初めは思いそうだけど
実際使ってみると、やりたい処理が複雑であればあるほどこの恩恵を実感できます。
画面出力するのにModelから返却された値をいじりたい、
それControllerでやるの?いやいや、、ないでしょ
Modelでやっておけばおkでは?いやいや、、おかしいでしょ
出力するView側であれこれいじって見せるの??いやいや、、汚いでしょ
等の板挟みにあった時にFuelPHPを使ってViewModelを使う選択をすればそのようなモヤモヤも一掃できます。
(ViewModelだった時の使用例は割愛します。)
Presenter、が来た
じゃあ1.8からViewModelから挿げ替わったPresenterはなんなの?という本題に入りますが
使い方はほぼ一緒です。
今回は member という名前のモジュールを作り、
一例としてDBから値を取得して、Viewに会社の採用メンバーの検索結果の一覧を表示させる、というイメージで作成します。
fuel/app下はこのような構造にします。
$ tree /xxxx/fuel/app
├── classes
│ ├── controller
│ │ ├── member
│ │ │ └── top.php
│ ├── model
│ │ └── member.php
│ └── presenter
│ ├── member
│ │ └── top.php
├── config
├── lang
├── logs
├── migrations
├── modules
├── tasks
├── tests
├── themes
├── tmp
├── vendor
└── views
└── member
└── top.php
Presenter のディレクトリはFuelインストール時の状態では存在していないので新たに作成します。
Controllerの書き方は以下のようになります。
class Controller_Member_Top extends Controller {
/**
* index
*/
public function action_index() {
$param = Input::post();
$display_data = Model_Member::get_member_data($param);
return Response::forge(
Presenter::forge('member/top')
->set('display_data', $display_data));
}
}
Modelは以下(データ取得部は割愛)
class Model_Member
{
/**
* 採用メンバー一覧取得
* @param string $search_param
* @return array 検索結果
*/
public static function get_member_data($search_param) {
....
return $search_list;
}
}
Presenterの例
class Presenter_Member_Top extends Presenter {
/**
* The default view method
* Should set all expected variables upon itself
*
* @return void
*/
public function view() {
$this->title = '採用メンバー一覧';
if (isset($this->display_data['member_list']) === true && count($this->display_data['member_list']) !== 0) {
foreach ($this->display_data['member_list'] as $key => &$recruit_member_info) {
$recruit_member_info['experience_data'] = '実務経験なし';
if (isset($recruit_member_info['experience_flg']) === true && $recruit_member_info['experience_flg']) {
$recruit_member_info['experience_data'] = '実務経験あり';
}
}
unset($recruit_member_info);
$this->search_list = $this->display_data['member_list']
}
}
このPresenterのviewメソッドの中で、
- Viewに表示するタイトルを設定
- Modelから取得した採用メンバーの情報をforeachで見てexperience_flg の値が 1 であったメンバーは「実務経験あり」と表示し、0 であるメンバーは「実務経験なし」と表示させるよう取得した情報を整形して設定しています。
$this->でメンバ変数を設定することで、設定した変数がView側で使用できます。
Viewは以下のようになります。
// html記述は割愛しています。
<title><?php echo $title; ?></title>
<?php foreach ($search_list as $key => $recruit_member_list): ?>
<?php echo $shop_data['name']; ?>
<?php echo $shop_data['experience_data']; ?>
<?php endforeach; ?>
Presenterで設定したtitle, search_list の配列の値がViewでそのまま記述するだけで使用できます。
設定された値を表示するだけなのでViewがスッキリします。
FuelでViewModelを使用していた人にとっては、
Controllerからforgeを用いてViewModelに値を渡していた箇所が
ViewModel::forge() → Presenter::forge()
に変わっただけですね。
Controllerでの記述は
$Presenter = Presenter::forge('member/top');
$Presenter->set('display_data', $display_data);
return Response::forge($Presenter);
と書いた方が分かりやすいかもしれないです。
Presenterの使い方についてはまだまだ深堀りが必要な部分もあるので随時更新します。
追記
Presenter::forgeについてもう少し詳しく
公式docを反芻。
例えばデバイスによってViewを出し分けたい場合
このように階層を作るとします。
$ tree /xxxx/fuel/app
├── classes
│ ├── controller
│ │ ├── member
│ │ │ └── top.php
│ ├── model
│ │ └── member.php
│ └── presenter
│ ├── member
│ │ └── top.php
...
└── views
└──pc
└──member
└──top.php
└──sp
└──member
└──top.php
しかしPresenterは共通でmember/top
を使いたいそんな場合。
// 独自のビューを使う
$presenter = Presenter::Forge('member/top', 'view', null, 'sp/member/top');
- 第1引数 presenterを指定
- 第2引数 presenterで使用するメソッド名を指定。デフォルトは
'view'
です。
公式では
同じビューで違ったレイアウトを生成するために、複数の準備メソッドを持つことができます。
と書いてますが、要は
class Presenter_Member_Top extends Presenter {
/**
* デフォルト
*/
public function view() {}
/**
* カスタムしたメソッド
*/
public function customview() {}
このように自分で作成したメソッドを指定して使えるわけですね。
- 第3引数 自動エンコーディングの設定するかしないかtrueかfalseを指定する
デフォだとapp/config/config.php
内の設定が読み込まれるのでnullになる模様。 - 第4引数 presenterと異なる階層のViewを読み込ませたい場合に指定する
今回は sp/ 配下のViewを使用したいのでこのように指定しています。
詳しく設定しない場合はPresenter::forge()
でpresenter名だけ指定するので、自ずとViewもその階層にならったもの、公式で言う所の「presenter名から判別できるView」を読み込もうとするんですね。
そうでない別のViewを読み込ませたい場合に使えます。