Yii フラッシュメッセージの機能をうまく再利用可能な形にするにはどの方法が一番優れていると思いますか

タイトルそのまんまですが、Yii でフラッシュメッセージの機能を再利用可能な形にする場合のベストプラクティスを探っていきます。

## 基本 

まず Yii でフラッシュの機能を作る基本の流れから。コントローラでセットして、ビューで一致したキーがあればメッセージを表示する、という感じでしょうか。

```php:HogeController.php
<?php
class HogeController extends Controller
{
	...
	public function actionCreate()
	{
		//データの追加処理
		Yii::app()->user->setFlash('success', 'データを追加しました');
		//リダイレクト処理
	}
	...
```

```php:index.phtml
<?php if (Yii::app()->user->hasFlash('success')): ?>
<div class="flash-success"><?php echo Yii::app()->user->getFlash('success'); ?></div>
<?php endif; ?>

<?php $this->widget('zii.widgets.CListView', array(
	...
)); ?>
```

再利用しなくてもこれで十分な気もしますね。ただ追加変更や修正が発生した場合に少しもろい感じもします。

## 部分ビューにする

ビューの特定の部分だけ別ファイルに移動してみます。

```php:flash.phtml
<?php
	$k = !isset($key) ? 'success' : $key;
	if (Yii::app()->user->hasFlash($k))
	{
		echo CHtml::openTag('div', array('class' => 'flash-'.$k));
		echo Yii::app()->user->getFlash($k);
		echo CHtml::closeTag('div');
	}
```

```php:index.phtml
<?php echo $this->renderPartial('//common/flash'); ?>

<?php $this->widget('zii.widgets.CListView', array(
	...
)); ?>
```

/protected/views/ に common ディレクトリを作って、そこに flash.php を作ってみました。各ビューでは flash.php を呼び出すだけです。これで追加変更や修正が発生しても楽ちんですね！ただ ... コントローラの記述も含めてもう少し短くできない？ Yii::app()->user->setFlash() とか $this->renderPartial() とか長いから、って要望が出たとします。 どうしましょうか。

## 親コントローラにまとめておく

```php:Controller.php
<?php
class Controller extends CController
{
	...
	public function setFlash($value, $key='success', $defaultValue=null)
	{
		Yii::app()->getUser()->setFlash($key, $value, $defaultValue);
	}

	public function getFlash($key='success')
	{
		if (Yii::app()->getUser()->hasFlash($key))
		{
			return CHtml::openTag('div', array('class' => 'flash-'.$key)).
				Yii::app()->getUser()->getFlash($key).
				CHtml::closeTag('div');
		}
	}
	...
```

親コントローラに、フラッシュのセット/ゲットメソッドを作ってみました。 この場合、各コントローラとビューではそれぞれ以下のように書くことができます。

```php:HogeController.php
<?php
class HogeController extends Controller
{
	...
	public function actionCreate()
	{
		//データ追加処理
		$this->setFlash('データを追加しました');
		//リダイレクト処理
	}
	...
}
```

```php:index.phtml
<?php echo $this->flash; ?>

<?php $this->widget('zii.widgets.CListView', array(
	...
)); ?>
```

コードの記述がシンプルになりましたね！ただ ... フラッシュメッセージを表示するときに出しっぱなしにするんじゃなくてちょっとしたら自動で消えるようにしてよ、って要望が出たとします。どうしましょうか。

```php:Controller.php
<?php
class Controller extends CController
{
	...
	public function getFlash($key='success')
	{
		if (Yii::app()->getUser()->hasFlash($key))
		{
			$attr = 'flash-'.$key;

			Yii::app()->clientScript->registerScript(
			   'flashHideAuto',
			   '$(".'.$attr.'").animate({opacity: 1.0}, 3000).fadeOut("slow");',
			   CClientScript::POS_READY
			);
			
			return CHtml::openTag('div', array('class' => $attr)).
				Yii::app()->getUser()->getFlash($key).
				CHtml::closeTag('div');
		}
	}
```

こうしますか？なんかあとあとまた追加変更や修正が発生した場合のことも考えると、んー ... って感じがしないでもないですね。どうしましょうか。

## ウィジェットにしてみる

```php:Flash.php
<?php
class Flash extends CWidget
{
	public $prefix = 'flash-';
	public $key = 'success';
	public $enableEffect = true;

	private $_hasFlash;

	public function init()
	{
		$this->_hasFlash = Yii::app()->getUser()->hasFlash($this->key);

		if ($this->enableEffect)
			$this->registerScript();
	}

	public function run()
	{
		if ($this->_hasFlash)
		{
			echo CHtml::openTag('div', array('class' => $this->prefix.$this->key));
			echo Yii::app()->getUser()->getFlash($this->key);
			echo CHtml::closeTag('div');
		}
	}

	public function registerScript()
	{
		$cs = Yii::app()->getClientScript();
		$cs->registerScript(
			'flashHideAuto',
			'$(".'.$this->prefix.$this->key.'").animate({opacity: 1.0}, 3000).fadeOut("slow");',
			CClientScript::POS_READY
		);
	}
}
```

protected/components/ に CWidget を継承した Flash.php を作ってみました。親コントローラで書いた setFlash() は残しておくことにすると、各コントローラとビューではそれぞれ以下のように書くことができます。

```php:HogeController.php
<?php
class HogeController extends Controller
{
	...
	public function actionCreate()
	{
		//データ追加処理
		$this->setFlash('データを追加しました');
		//リダイレクト処理
	}
	...
}
```

```php:index.phtml
<?php $this->widget('Flash'); ?>

<?php $this->widget('zii.widgets.CListView', array(
	...
)); ?>
```

お、何か親コントローラに書くよりも良い感じな気がしますね！ただ ... このページはメッセージだしっぱなしにしておいて、って要望があったとします。どうしましょうか。

```php:somewhere.phtml
<?php $this->widget('Flash', array('enableEffect' => false)); ?>
...
```

すばやい対応ができましたね！ただ ... 全体的にメッセージが消えるタイミングをもう少し早くできない？あと div タグの class名 が flashMessage になったから修正しておいて、って要望があったとします。どうしましょうか。でももう追加変更、修正が発生した場合でも、ウィジェットじゃくて他の方法を考える必要はなさそうです。

## まとめ

ということで 自分は Yii でフラッシュメッセージの機能を再利用可能な形にする場合のベストプラクティスは、追加変更、修正などが発生した場合でも柔軟に対応できる点からしてウィジェットに落ち着きました。皆さんはどうでしょうか？今回の例では出てきていませんが、コンポーネントやヘルパーでも作れそうな気もしますが、まだまだ勉強不足なので、他にも良いアイデアがあれば是非教えてください！