Paginateオプションをモデルに移行する
注意
改善記事を書きました。この記事の例ではうまく動きません。
paginateオプションをモデルに移行する(改善・修正版) - 24時間CakePHP http://d.hatena.ne.jp/hiromi2424/20100609/1276076490
概要
Paginatorを使うとき、group byして集計関数を使ったりすると、コントローラでpaginateのオプションが肥大化する場合があります。
このTipsでは、virtualFieldsを動的に使うテクニック等を用いることによって、コントローラからモデルにロジックを移行するティップスを紹介します。
本題
まず、ごりっとpaginateしてみます。
ユーザはレート(評価)を持っていることとします。
グループごとのユーザのレートの平均を集計します。
<?php // controller class GroupsController extends AppController{ function index(){ $this->paginate = array( 'fields' => array('Group.id','Group.name','user_rate_average'), 'group' => 'Group.id', 'joins' => array( array( 'type' => 'LEFT', 'table' => 'users', 'alias' => 'User', 'conditions' => 'Gourp.id = User.group_id', ) ), ); $this->set('groups', $this->paginate('Group')); } } // model class Group extends AppModel{ var $virtualFields = array( 'user_rate_average' => 'AVG(User.rate)', ); }
コントローラのアクションが肥大化してあまりよろしくありません。そこで、次のようにします。
<?php // controller class GroupsController extends AppController{ function index(){ $this->Group->paginateType = 'user_avg'; $this->set('groups', $this->paginate('Group')); } } // model class Group extends AppModel{ var $paginateType = 'default'; var $options = array( 'user_avg' => array( 'fields' => array('Group.id','Group.name','user_rate_average'), 'group' => 'Group.id', 'joins' => array( array( 'type' => 'LEFT', 'table' => 'users', 'alias' => 'User', 'conditions' => 'Gourp.id = User.group_id', ) ), ), ); function paginate($conditions, $fields, $order, $limit, $page, $recursive, $extra){ $parameters = compact('conditions', 'fields', 'order', 'limit', 'page'); if ($recursive != $object->recursive) { $parameters['recursive'] = $recursive; } $parameters += $this->options($this->paginateType); // ここだけオリジナルと違う $results = $this->find($this->paginateType, array_merge($parameters, $extra)); } function paginateCount($conditions, $recursive, $extra){ $parameters = compact('conditions'); if ($recursive != $object->recursive) { $parameters['recursive'] = $recursive; } $parameters += $this->options($this->paginateType); // ここだけオリジナルと違う return $this->find('count', array_merge($parameters, $extra)); } function options($type){ return isset($this->options[$type]) ? $this->options[$type] : array(); } function find($type, $params = array()){ switch($type){ case 'user_avg': // 古いやつは残しておく $old_vf = $this->virtualFields; // 動的に設定 $this->virtualFields = array( 'user_rate_average' => 'AVG(User.rate)', ); $result = parent::find('all', $params); //古いやつを元に戻す $this->virtualFields = $old_vf; return $result; default: if(!array_key_exists($type, $this->_findMethods){ $type = 'all'; } break; } return parent::find($type, $params); } }
今度はモデルが肥大化してるじゃん!超だめじゃん!と思われるかもしれませんが、
- Controllerは最小
- virtualFieldsを動的に配置したことにより他の操作(ページネートした後にfind())をする場合、virtualFieldsが邪魔することの回避
- モジュール分解が適切にできている。よって拡張が容易
- AppModelに汎用メソッドを定義すればさほどModel自体は肥大化しない(4/27 3:58追記)
といった利点が挙げられます。
最後に
もう少し踏み込めば、汎用化することもできます。
そうなれば、よりrapidな開発ができることは明白ですね:D