モデルの配列を管理する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で定義するのが望ましいです。
まとめ
findオプションが煩雑になればなるほど、この機能は真価を発揮します。
また、find('list' or 'count')は使わない場合もありますが、findする時は常にこのオプションを使うようにすることはメンテナンス性を高めます。
例えば、作成日降順にしたり、無効フラグが立っていないことを後付けで全てに適用することがすぐにできるようになります。
もちろん、optionsをまったく定義しないモデルが存在してもそれはごく普通です。
例えば常に親テーブルが子テーブルを引っ張ってくるようなものなら、親テーブルのモデルに全てのオプションを記述すればいいだけです。
このビヘイビアを使うことによって、非常にシンプルな記述が可能になり、可読性の向上も望めるでしょう。
fatコントローラから脱却する第一歩でもあります。是非、使用を検討してみてください :D