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);
	}
}

今度はモデルが肥大化してるじゃん!超だめじゃん!と思われるかもしれませんが、

といった利点が挙げられます。

最後に

もう少し踏み込めば、汎用化することもできます。
そうなれば、よりrapidな開発ができることは明白ですね:D