FuelPHPのプレゼンタ(Presenter)を使う


FuelPHPのプレゼンタ(Presenter)について使い方を調べてみました.

# プレゼンタとは

MVC(Model-View-Controller)モデルの派生であるMVP(Model-View-Presenter)モデルのPresenterのような機能です.
MVPモデルそのものについては, こちらの記事が参考になりました.

* [Webアプリケーション開発者から見た、MVCとMVP、そしてMVVMの違い](http://qiita.com/shinkuFencer/items/f2651073fb71416b6cd7)
* [StackOverFlowの「MVPとMVCの違い」についての回答を読んでみた](http://qiita.com/takahirom/items/597c48ece57b4623cdee)

もう少し具体的に, FuelPHPのマニュアルから, FuelPHPが言うところのプレゼンタの役割を抜粋してみました.

* ビューに関するロジックを抽象化してコントローラから切り離すために使う
* データの操作はすべきでない
* データベース他の呼び出しを含める
* ビューの生成に必要なデータを準備する操作を含める

FuelPHPのプレゼンタについては, こちらの記事が参考になりました.

* [一生涯プログラマ : FuelPHPのViewModelについて](http://blog.livedoor.jp/rapple_lab/archives/6534192.html)

Presenterクラスはバージョン1.7.2でViewModelクラスから改名された経緯があります.

* [FuelPHP &raquo; FuelPHP releases v1.7.2 | Blog](http://fuelphp.com/blogs/2014/07/fuelphp-releases-v1-7-2)
* [1.7.1までViewModelだったのが1.7.2でPresenterになったFuelPHPの話をしよう](http://qiita.com/miyatah/items/4187af53d7c359af63e8)

# プレゼンタを使う

サンプルとして, 蔵書システムの出版社情報を管理する機能を考えます.
出版社情報を登録する入力フォームを表示するアクションメソッドを, Viewクラスを使って記述すると, このようになります.

```php:fuel/app/classes/controller/publisher.php
class Controller_Publisher extends Controller
{
	public function get_create()
	{
		$form = Fieldset::forge('publisher')->add_model(Model_Publisher::forge());
		$form->field('name')->add_label(_(publisher.name));
		$form->add('submit', '', array(
			'type' => 'submit',
			'class' => 'btn btn-primary submit',
			'value' => 'Create',
		));
		$form->populate(Model_Publisher::forge());

		return Response::forge(
			View::forge('publisher/create')
				->set('title', 'Publishers')
				->set_safe('form', $form->build())
		);
	}
}
```
これをPresenterクラスを使って記述するように変更してみます.

## プレゼンタクラスを作成する

`Controller_Publisher::get_create()` の中身は入力フォーム生成のための処理なので, その処理をごっそりプレゼンタへ移します. 値をビューに渡す処理のみ調整します.

```php:fuel/app/classes/presenter/publisher/create.php
class Presenter_Publisher_Create extends Presenter
{
	public function view()
	{
		$form = Fieldset::forge('publisher')->add_model(Model_Publisher::forge());
		$form->field('name')->add_label(_(publisher.name));
		$form->add('submit', '', array(
			'type' => 'submit',
			'class' => 'btn btn-primary submit',
			'value' => 'Create',
		));
		$form->populate(Model_Publisher::forge());

		$this->set('title', 'Publishers');
		$this->set_safe('form', $form->build());
	}
}
```

## コントローラでPresenterオブジェクトを生成する

コントローラでは, Viewオブジェクトの代わりにPresenterオブジェクトを生成します. `Presenter::forge()` の第2引数にはプレゼンタのメソッド名を指定します. 未指定の場合は `view` を指定することになります.

```php:fuel/app/classes/controller/publisher.php
class Controller_Publisher extends Controller
{
	public function get_create()
	{
		return Response::forge(
			Presenter::forge('publisher/create')
		);
	}
}
```

コントローラが随分すっきりしました.

## コントローラからプレゼンタへ値を渡したい...?

POSTされたidでfindした情報を表示したい, みたいな場合です.
出版社情報を表示するアクションメソッドを, Viewクラスを使って記述すると, このようになります.

```php:fuel/app/classes/controller/publisher.php
class Controller_Publisher extends Controller
{
	public function action_view($id = null)
	{
		is_null($id) and Response::redirect('publisher');

		if ( ! $publisher = Model_Publisher::find($id))
		{
			Session::set_flash('error', 'Could not find publisher #'.$id);
			Response::redirect('publisher');
		}

		return Response::forge(
			View::forge('publisher/view')
				->set('title', 'Publisher')
				->set('publisher', $publisher)
		);
	}
}
```

これをプレゼンタを使って記述すると, ビューに必要な `$id` をコントローラからプレゼンタへ渡したくなりますが, プレゼンタから `$id` が取得できるなら, ビューに関する処理としてプレゼンタに閉じ込めることができそうです.
今回の場合は, ルーティングの設定でパラメータの名前を設定して `Request::param()` で取得できるようにします.

```php:fuel/app/config/routes.php
return array(
	'publisher/view/:id' => 'publisher/view/$1',
);
```
これでプレゼンタからモデルへアクセスして値が取得できるようになりました.

```php:fuel/app/classes/presenter/publisher/view.php
class Presenter_Publisher_View extends Presenter
{
	public function view()
	{
		$this->set('title', 'Publisher');
		$this->set('publisher', Model_Publisher::find(Request::active()->param('id')));
	}
}
```

コントローラではPresenterオブジェクトを生成するのみです.

```php:fuel/app/classes/controller/publisher.php
class Controller_Publisher extends Controller
{
	public function action_view($id = null)
	{
		is_null($id) and Response::redirect('publisher');

		if ( ! Model_Publisher::find($id))
		{
			Session::set_flash('error', 'Could not find publisher #'.$id);
			Response::redirect('publisher');
		}

		return Response::forge(
			Presenter::forge('publisher/view')
		);
	}
}
```

## Templateコントローラとの併用

どうしてもコントローラでテンプレートにデータを渡すことになるので, ビューに関する処理がコントローラから切り離せません.
Templateコントローラとプレゼンタは併用しないほうが, プレゼンタの機能が活きるように思います.