モデルの配列を管理するCollectionableプラグイン - Optionsビヘイビア

Collectionableプラグインとは

モデルのメソッドの引数やプロパティには、大きな配列が用いられるものがあります。
この管理を適当にすると、コピペの嵐になりがちです。
しかし毎度毎度、その管理の為のコードを書くのは億劫ですし、ロジックのミスにより予期しないバグの混入に陥ることがあります。
Collectionableプラグインは、そのような汎用的な配列の管理を提供するためのもので、配列の共通部分を減らし、かつ柔軟な切り替えを行うことを可能にします。


このプラグインGitHubで公開しています。以下からダウンロードするかcloneするかsubmoduleとしてご利用ください。
また、英語ですがreadmeにはサンプルコードを載せてあります。
hiromi2424/Collectionable - GitHub


このプラグインには現在4つのビヘイビアが存在します。今回は、そのうち最もよく使うOptionsビヘイビアについて解説します。

Optionsビヘイビア

以前、モデルのfindオプションについて(特にPaginateに絞って)二つの記事を書きました。


Paginateオプションをモデルに移行する - 24時間CakePHP
paginateオプションをモデルに移行する(改善・修正版) - 24時間CakePHP


この記事に書いてあることを概ねビヘイビア化したものがOptionsビヘイビアVirtualFieldsビヘイビアです。
ただし、この記事のコードよりも高機能です。
そしてOptionsビヘイビアは、findオプションを管理する役割を担います。

'options'属性、options()

オプションの指定の仕方は、二通りあります。
一つはfindのオプションに指定することです。

<?php
$Model->find('all', array('options' => 'recent')); // recentオプションを指定する

もう一つはビヘイビアによって設定されるoptionsメソッドを呼ぶことです。

<?php
$Model->options('recent'); // findオプションを返す
$optionsプロパティ

モデルのオプションを管理するための配列を指定する場所は、モデルの$optionsプロパティです。
$optionsは名前がキー、値がfindオプションの単純な連想配列です。
以下のように記述します。

<?php

class User extends AppModel {

	public $options = array(
		'recent' => array(
			'limit' => 10,
			'conditions' => array(
				'active' => true,
			),
		),
	);

}
単純な使い方

コントローラなどで単純にfindの結果を取得したり、paginationを行う際、直接呼び出すことになるでしょう。
例を挙げます。

<?php

class UsersController extends AppController {

	public function index() {
		$this->paginate = $this->User->options('index');
		$this->set('users', $this->paginate());
	}

	public function recent() {
		$this->set('users', $this->User->find('all', array('options' => 'recent')));
	}

}
実践的な使い方

ここからが本題です。
単純な使い方ではそのメリットが分からないと思います。
コントローラで記述していたものがモデルに移っただけではありません
Optionsビヘイビアには以下のような機能が備わっています。

  • デフォルトのオプション
  • 複数のオプションの指定(マージ)
  • オプションにオプションを含めることができる(再帰なマージ)

以下にReadmeから抜粋して、コメントを訳したものを提示します。


単純な投稿を扱うPostモデルがあるとします。

<?php

class Post extends AppModel {
	var $hasMany = array('Comment');
	var $hasOne = array('Status');

	var $acsAs = array('Collectionable.options', 'Containable');
	var $defaultOption = true; // デフォルトでこの値、'default'がデフォルトのオプションになる。もしくは文字列を指定してデフォルトのオプションを指定する

	var $options = array(
		'default' => array(
			'contain' => array(
				'Comment',
				'Status',
			),
			'limit' => 10,
		),
		'published' => array(
			'condtiions' => array('Status.published' => true),
		),
		'recent' => array(
			'order' => ('Post.updated DESC'),
		),
		'rss' => array(
			'limit' => 15,
		),
		'unlimited' => array(
			'limit' => null,
		),
		'index' => array(
			// このようにマージができるので、書き直す必要は無い
			'options' => array(
				'published',
				'recent',
			),
		),
	);
}

これを以下のようにして使えます。

<?php
class PostsController extends AppController {
	function index() {
		$this->paginate = $this->Post->options('index');
		$this->set('posts', $this->paginate());
	}

	function rss() {
		$this->paginate = $this->Post->options('index', 'rss'); // 実行時に複数オプションを指定できる
		$this->set('posts', $this->paginate());
	}

	function all_in_one_page() {
		$posts = $this->Post->find('all', array('options' => array('index', 'unlimited')));
		$this->set(compact('posts'));
	}
}
より高度な使い方

全てのfindに対してデフォルトオプションを定義する

<?php
class Post extends AppModel {

	var $acsAs = array('Collectionable.options', 'Containable');

	public function find($type, $query = array()) {
		if (is_array($query) && empty($query['options'])) {
			$query['options'] = 'default';
		}
		return parent::find($type, $query);
	}

}

options()の挙動をオーバーライドする

<?php
class Post extends AppModel {

	var $acsAs = array('Collectionable.options', 'Containable');

	public function options() {
		$args = func_get_args();
		// 何かする
		$result = call_user_func_array(array($this->Behaviors->Options, 'options'), $args);
		// 何かする
		return $result;
	}

}
Containableが動かないなど

オプションによって挙動が変わる他のビヘイビアを同時に使う場合は、$actsAsの先頭にOptionsビヘイビアが来るようにしてください。
これはAppModelで定義するのが望ましいです。

今後の展望

以下のものなどを考えています。
もしこういう機能が欲しいなどありましたら、気軽にTwitterなどで聞いてみてください。
フィードバックは宝物です :)

  • Configureの指定、モデルのエイリアスの指定などを楽に
  • コールバックの実装

まとめ

findオプションが煩雑になればなるほど、この機能は真価を発揮します。
また、find('list' or 'count')は使わない場合もありますが、findする時は常にこのオプションを使うようにすることはメンテナンス性を高めます
例えば、作成日降順にしたり、無効フラグが立っていないことを後付けで全てに適用することがすぐにできるようになります。


もちろん、optionsをまったく定義しないモデルが存在してもそれはごく普通です。
例えば常に親テーブルが子テーブルを引っ張ってくるようなものなら、親テーブルのモデルに全てのオプションを記述すればいいだけです。


このビヘイビアを使うことによって、非常にシンプルな記述が可能になり、可読性の向上も望めるでしょう。


fatコントローラから脱却する第一歩でもあります。是非、使用を検討してみてください :D