paginateオプションをモデルに移行する(改善・修正版)
過去記事の問題点
Paginateオプションをモデルに移行する - 24時間CakePHP http://d.hatena.ne.jp/hiromi2424/20100426/1272289702
では、paginateオプション内に"order"や"limit"が無いため、これらオプションを指定すると動作が謎になってしまいました。
そこで、AppControllerにページネートタイプを指定するとともにオプションをセットする機構を設け、この問題点をクリアします。
向上点
- virtualFieldsをコレクションとすることで、デフォルトのvirtualFieldsと追加のvirtualFieldsの親和が容易になり、指定の幅も広がりました。
準備
AppController,AppModelに汎用メソッド・プロパティを定義します。
AppController
<?php class AppController extends Controller { function setPaginateType($model, $type = null){ if($type === null){ $type = $model; $model = $this->modelClass; } if(empty($model) || $model == 'App'){ return false; } $this->paginate[$model] = $this->$model->options($type); $this->$model->paginateType = $type; return true; } }
AppModel
<?php class AppModel extends Model { var $paginateType = null; var $options = array(); var $virtualFieldsCollection = array(); function paginate($conditions, $fields, $order, $limit, $page, $recursive, $extra){ $parameters = compact('conditions', 'fields', 'order', 'limit', 'page'); if ($recursive != $this->recursive) { $parameters['recursive'] = $recursive; } // $parameters = Set::merge($parameters, $this->options($this->paginateType)); $type = ($this->paginateType === null && isset($extra['type'])) ? $extra['type'] : $this->paginateType; $results = $this->find($type, array_merge($parameters, $extra)); return $results; } function paginateCount($conditions, $recursive, $extra){ $parameters = compact('conditions'); if ($recursive != $this->recursive) { $parameters['recursive'] = $recursive; } // $parameters = Set::merge($parameters, $this->options($this->paginateType)); $results = $this->find('count', array_merge($parameters, $extra)); return $results; } function options($type){ return isset($this->options[$type]) ? $this->options[$type] : array(); } function find($type, $params = array()){ if (!array_key_exists($type, $this->_findMethods)) { if (isset($params['virtualFields'])) { $virtualFields = Set::normalize($params['virtualFields']); unset($params['virtualFields']); foreach ($virtualFields as $key => $sql) { if (empty($sql)) { if (isset($this->virtualFieldsCollection[$key])) { $virtualFields[$key] = $this->virtualFieldsCollection[$key]; } else { unset($virtualFields[$key]); } } } if (!empty($virtualFields)){ $tempVirtualFields = $this->virtualFields; $this->virtualFields = array_merge($this->virtualFields, $virtualFields); $result = parent::find('all', $params); $this->virtualFields = $tempVirtualFields; return $result; } } $type = 'all'; } return parent::find($type, $params); } }
実装例
例ではPostモデルを使います。
- Post hasMany Comment
- コメントの数を同時に取得します。
コントローラ
<?php class PostsController extends AppController { var $name = 'Posts'; function index() { $this->setPaginateType('public'); $this->set('cards', $this->paginate()); } function search() { // GETフォームを使った簡単な検索 if (isset($this->params['url']['word'])) { $this->setPaginateType('public'); $word = '%' . $this->params['url']['word'] . '%'; $this->set('posts', $this->paginate(array('Post.name like' => $word))); } }
モデル
<?php class Post extends AppModel { var $name = 'Post'; var $actsAs = array('Matchable'); var $options = array( 'public' => array( 'contain' =>false, 'jointo' => 'Comment', 'fields' => array( '*' ), 'conditions' => array( 'Post.public' => 1, ), 'group' => 'Post.id', 'order' => 'Post.created DESC', 'virtualFields' => array('comments_count'), // 文字列'comments_count'でもいいよ! ), ); $this->virtualFieldsCollection = array( 'comments_count' => 'COUNT(Comment.id)', ); }
ちなみにMatchableビヘイビアを使ってみたりしました。
実コントローラ、実モデルが異様にすっきりと、かつコードがくっきりと意味を表してすらっと読めますね。
options()を実モデルでオーバーライドすれば、optionのマージとかもできるようになり、更に使いまわしが効きます。
ただ、virtualFieldsパラメータの実装に関してはビヘイビアに落としたほうがいいかもしれません。AppModelが膨らみすぎてる感があります。
余談
このように、自分好みの汎用的な機構を作って、開発を効率的にすることで、フレームワークの力を最大限に引き出すことができます。
さぁ、あなたも今すぐ自分のコードを見直して、新しい機構を作ってみましょう!(そして公開するとみんなが幸せになれます)