Yii コードの再利用と、パラメータの初期値について

Yiiはビューで利用するテーブルカラムのラベルでさえ、モデルの attributeLabels() というメソッドを使って一元管理できるようになっています。変更が発生した場合でも、このメソッド内のラベル名を変更するだけでいいので楽ちんです。ビューには一切手をつけません。このように、Yiiでは細かな部分も含めて、あらゆるところで再利用性を意識した設計になっているので、今回はそれらについて、簡単にですが説明していきたいと思います。

## アプリケーション初期構成ファイル

いわゆる protected/config/main.php ファイルです。アプリケーション全体の初期構成をここに書いていきます。[API: CWebApplication](http://www.yiiframework.com/doc/api/1.1/CWebApplication) でデフォルトの設定などを確認できます。また、パブリックなプロパティはすべて変更できるので、一度ざっと目を通しておくといいかもです。ここで設定した値はアプリケーション全体に影響を及ぼす点からして、再利用の宝庫なので、まずはここからはじめたいと思います。

---

```php:main.php
<?php
return array(
	...
	'name' => 'My Web Application',
	...
);
```

これはアプリケーション名の設定です。 Yii::app()->name でアクセスします。レイアウトやビュー、ページタイトルやメールで扱ったりしますね。また、環境のモードによって名前を変更することもできます。`'name' => YII_DEBUG ? 'Dev My Web Application' : 'My Web Application',` 

---

```php:main.php
<?php
return array(
	...
	'params' => array(
		'title' => 'My Yii Blog',
		'adminEmail' => 'webmaster@example.com',
		'postsPerPage' => 10,
		'recentCommentCount' => 10,
		'tagCloudCount' => 20,
		'commentNeedApproval' => true,
		'copyrightInfo' => 'Copyright &copy; 2012 by My Company.',
	),
);
```

デフォルトだと一番下の方にあります。上記の設定の例は、デモブログのものを借りました。アプリケーション全体で扱う定数みたいなもの、という位置でしょうか。Yii::app()->params\['パラメータ名'\] でアクセスします。10 とか 20 とか直接書いちゃいけない。 const などを使うか、ここで設定しておきましょう。

---

```php:main.php
<?php
return array(
	...
	'language' => 'ja',
	...
	'components' => array(
		'coreMessages' => array(
			'basePath' => null,
		),
		...
```

コアの例外発生時のメッセージや、バリデーションエラー時のメッセージなどを日本語化し、かつカスタマイズしたいときは上記のように設定します。あとは /framework/messages/ja/ のファイルをコピーして /protected/messages/ja/ に置いて、中身を以下のように書き換えます。 

```php:yii.php
<?php return CMap::mergeArray(require(Yii::getFrameworkPath().'/messages/ja/yii.php'), array(
	'{attribute} cannot be blank.' => '{attribute}が入力されていません。',
	...
));
```

全体をコピーして差し替えるやり方もありますが、バージョンアップするたびにメッセージが増えたりするので、こっちのが楽かな？と今記事を書きながら試してみて思いました。このファイルをどこかに保存しておけば、他のアプリケーションを作るときにも再利用できますね！

---

```php:main.php
<?php
return array(
	..
	'components'=>array(
		'widgetFactory' => array(
			'widgets' => array(
				'CCaptcha' => array(
					'clickableImage' => true, // 画像クリックでキャプチャ画像の変更を許可するかどうか
					'showRefreshButton' => false, // 画像の横にリフレッシュボタン、またリフレッシュリンクを表示するかどうか
				),
				...
			),
		),
		...
```

ウィジェットの初期値を予め設定しておくやり方です。ビューなどでウィジェットのコードを書くときに、何度も同じようなことを書いている場合は widgetFactory を利用して、各ウィジェットの初期値を設定しておきましょう。変更が発生した場合も修正が一箇所で済み、管理も楽です。

---

## モデル

```php:Hoge.php
<?php
class Hoge extends CActiveRecord
{
	...
	public function defaultScope()
	{
		$alias = $this->getTableAlias(false, false);

		return array(
			'condition' => $alias.'.is_deleted = 0',
		);
	}
	...
```

特定のモデルのすべてのクエリに適用されるデフォルトのスコープを設定することができます。 上記のように設定した場合、find(), findAll() などにアクセスしたときに 'ls_deleted = 0' が自動的に付加されます。これで 'is_deleted = 0' などを何度も書かなくてよくなりますね。defaultScope() に設定されているものを一時的にキャンセルしたい場合は resetScope() を使います。 例えば `Hoge::model()->resetScope()->findAll();` な感じです。

---

```php:Hoge.php
<?php
class Hoge extends CActiveRecord
{
	...
	public function scopes()
	{
		return array(
			'published' => array(
				  'condition' => 'status = 1',
			),
			'recently' => array(
				  'order' => 'create_time DESC',
				  'limit' => 5,
			),
			...
```

scopes() メソッドを追加することで、あらかじめよく使うであろうスコープを設定しておくこともできます。上記のようなコードの場合 `Hoge::model()->published()->findAll()` という感じで使います。これで 'status = 1' がクエリに付加されます。クエリの管理が楽になり、より再利用性が高くなりますね。

---

その他、モデルで再利用可能なコードを書く場合には ビヘイビア というものを利用できます。例えばデータの保存時にタイムスタンプを自動で挿入したいときには、 beforeSave() などに書くことになりますが、これも各モデルの beforeSave() に書かずに、一元管理したほうがいいでしょう。

```php:TimestampBehavior.php
<?php
class TimestampBehavior extends CActiveRecordBehavior
{
    public function beforeSave($event)
    {
        if($this->owner->isNewRecord)
            $this->owner->create_time=time();
        else
            $this->owner->update_time=time();
    }
}
```

上記のようなファイルを protected/components/ に作って、このビヘイビアを使いたいモデルで以下のようにして呼び出します。これでデータ挿入時、または更新時に自動的にタイムスタンプを挿入できるようになります。

```php:Hoge.php
<?php
class Hoge extends CActiveRecord
{
	...
	public function behaviors()
	{
		return array(
			'TimestampBehavior' => array(
				'class' => 'application.components.TimestampBehavior',
			),
		);
	}
}
```

カラム名に修正があった場合や、カラムの型変更が発生した場合などを考えると、やはり管理、修正が楽になります。

---

## コントローラ

続いてはコントローラです。`yiic webapp` を実行し、アプリケーションの雛形を作成した時点で、すでに再利用性を考えた構成になっているものがいくつもあります。例えば protected/components/ の Controller.php などがそれにあたります。各ビューで扱うレイアウトファイルが一緒の場合は Controller.php に設定しておいて、各コントローラはそれを継承する形を取ると、レイアウトの設定をいくつもの場所で書かなくて済みます。プロパティ以外では、よく使うであろうメソッドをあらかじめ設定しておくことのもいいでしょう。以下はデータを1件取得するメソッドの例です。各コントローラでは $this->loadModel() でアクセスすることができます。

```php:Controller.php
<?php
Class Controller extends CController
{
	...
	public function loadModel()
	{
		$modelClass = new $this->_modelClassName();
		$model = $modelClass->findByPk(Yii::app()->request->getQuery('id'));

		if ($model === null)
			throw new CHttpException(404, 'データがありません');

		return $model;
	}
	...
```

---
  
また、それぞれのコントローラでも init() メソッドを利用することによって、そのコントローラで扱いたい値を初期値として持たせておくこともできます。

```php:HogeControlller.php
<?php
class HogeController extends Controller
{
	private $_ip;

	public function init()
	{
		$this->_ip = Yii::app()->request->userHostAddress;
	}

	public function actionFuga()
	{
		$model = new Hoge();
		$model->ip = $this->_ip;
		...
	}

	public function actionPiyo()
	{
		$models = Hoge::model()->getAllByIp($this->_ip);
		...
	}
}
```

---

その他では アプリケーションコンポーネント というものもあります。Controller.php に書くほど一般的ではないものを アプリケーションコンポーネント という形にまとめておくやり方です。 protected/components/ に 例えば MyComponent.php を作成し、以下のように書きます。

```php:MyComponent.php
<?php
class MyComponent extends CApplicationComponent
{
	public $a = 'a';

	public function init()
	{
	}

	public function b()
	{
		return 'b';
	}
}
```

次に protected/config/main.php に作ったアプリケーションコンポーネントを構成します。パブリックなプロパティはここでも変更がききます。

```php:main.php
<?php
return array(
	...
	'components' => array(
		'mycomp' => array(
			'class' => 'MyComponent',
			// 'a' => 'a!', // デフォルト値の変更例
		),
		...
```

あとは適当な場所で `Yii::app()->mycomp->b();` などとすると、独自で作ったアプリケーションコンポーネントにアクセスできるようになります。

---

Yiiではアクション単位での再利用もわりと簡単にできます。例えばデータを1件追加するアクションを再利用する形で書く場合は以下のようになります。protected/components/actions/ に CreateAction.php を作成します。(ファイルの設置場所は特に決まりはありません)

```php:CreateAction.php
<?php
class CreateAction extends CAction
{
	public $renderView = 'create';

	public function run()
	{
		$modelClassName = ucfirst($this->controller->id);
		$model = new $modelClassName();

		if (isset($_POST[$modelClassName]))
		{
			$model->attributes = $_POST[$modelClassName];
			if ($model->save())
				$this->controller->redirect(array('view', 'id' => $model->id));
		}

		$this->controller->render($this->renderView, compact('model'));
	}
}
```

CAction を継承したクラスを書いて、 run() メソッドにアクションコードを書いていきます。普段コントローラで扱っている $this は CAction を継承したクラスの場合 $this->controller または $this->getController() になります。モデル名を取得できないので、コントローラIDの頭を大文字にすることによって取得するようにしています。コントローラでは、以下のように書くことによって CreateAction が利用できるようになります。

```php:HogeControlller.php
<?php
class HogeController extends Controller
{
	public actions()
	{
		return array(
			'create' => array(
				'class' => 'application.components.actions.CreateAction',
				//'renderView' => 'add', プロパティの変更例
			),
		);
	}
}
```

プロパティをいくつか持たせておくことによって、多少柔軟なアクションにも対応できます。

---

## ビュー

ビューではレイアウト、部分ビュー、ウィジェットなどを使って再利用可能な形に構成できます。レイアウトはコントローラのところでも少し話したので省略して、部分ビューから。 (ウィジェットの作成も少し複雑になるので省略します)

---

ビューのコードの中で部分的に重複する箇所がある場合などには CController::renderPartial() を利用できます。例えば追加アクションと更新アクションとでフォームの形が一緒の場合などには便利かなと思います。以下はブログデモで使われているやり方です。

```html+php:create.php
...
<h1>Create Post</h1>

<?php echo $this->renderPartial('_form', array('model' => $model)); ?>
```

```html+php:update.php
...
<h1>Update <i><?php echo CHtml::encode($model->title); ?></i></h1>

<?php echo $this->renderPartial('_form', array('model' => $model)); ?>
```

```html+php:_form.php
<div class="form">

<?php $form = $this->beginWidget('CActiveForm'); ?>

	<?php echo CHtml::errorSummary($model); ?>

	<div class="row">
		<?php echo $form->labelEx($model, 'title'); ?>
		<?php echo $form->textField($model, 'title', array('size' => 80,'maxlength' => 128)); ?>
		<?php echo $form->error($model, 'title'); ?>
	</div>
	...
	<div class="row buttons">
		<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
	</div>

<?php $this->endWidget(); ?>

</div><!-- form -->
```

変数を渡したい場合は第2引数で key=>value の配列の形にします。また部分ビューのファイル名をアンダースコアではじめることによって、その他のビューと区別することができます。

---

## 備考

Yiiでの再利用を考えるとその他にはモジュールやテーマ、あとエクステンションとしてまとめる方法だったり、いろいろあるかと思いますが、ボリュームが多すぎて書ききれないのでまたの機会にします。あと自分もまだYiiを深く理解しているとは言いがたいので、他にもこういう再利用方法があるよ？って方は是非教えてください！

## 参考リンク

* [API: CWebApplication](http://www.yiiframework.com/doc/api/1.1/CWebApplication/)
* [Guide: ウィジェットをグローバルにカスタマイズする](http://www.yiiframework.com/doc/guide/1.1/ja/topics.theming#sec-5)
* [Guide: エクステンションの作成](http://www.yiiframework.com/doc/guide/1.1/ja/extension.create)
* [Wiki: Actions code reuse with CAction](http://www.yiiframework.com/wiki/170/actions-code-reuse-with-caction/)
* [Wiki: Extending common classes to allow better customization](http://www.yiiframework.com/wiki/121/extending-common-classes-to-allow-better-customization)