I've custom find type method in User model:
protected function _findStats($state, $query, $results = array()) {
if ($state === 'before') {
debug($query);
$query['fields'] = array('User.id', 'User.username', 'Click.clicks', 'Click.unique', 'Sale.sales_1', 'Sale.sales_2');
$query['joins'] = array(
array(
'table' => '(' . $this->__buildSubQueryClicks() . ')',
'alias' => 'Click',
'type' => 'LEFT',
'conditions' => array('User.id = Click.id')
),
array(
'table' => '(' . $this->__buildSubQuerySales() . ')',
'alias' => 'Sale',
'type' => 'LEFT',
'conditions' => array('User.id = Sale.id')
)
);
return $query;
}
return $results;
}
Now from other Controller (named ReportsController
) I would like to paginate results of this find. I'm doing it like this:
public function admin_index() {
list($from, $to) = $this->_prepareCommonVariables();
$this->paginate = array('stats');
$this->User->recursive = 0;
$this->set('users', $this->paginate('User',
array(
'Click.created BETWEEN ? AND ?' => array($from, $to),
'Sale.sold BETWEEN ? AND ?' => array($from, $to)
),
array('User.username', 'Click.unique', 'Click.clicks', 'Sale.sales_1', 'Sale.sales_2')
));
}
The problem is that I can only order by User.id
and User.username
fields (generally User-fields). Even if I specified the allowed fields by third parameter of Controller::paginate()
it doesn't work. The Paginator Helper
links generate proper URLs (eg. /admin/reports/index/sort:Click.clicks/direction:asc
) but I don't see ORDER BY part in SQL query. I've put debug($query)
in _findStats()
and when the field is for example Click.clicks
the $query['order']
is empty.
Ok, I've found that $query also recive 'sort' and 'direction' keys. So, here goes the solution:
protected function _findStats($state, $query, $results = array()) {
if ($state === 'before') {
$query['fields'] = array('User.id', 'User.username', 'Click.clicks', 'Click.unique', 'Sale.sales_1', 'Sale.sales_2');
$query['joins'] = array(
array(
'table' => '(' . $this->__buildSubQueryClicks() . ')',
'alias' => 'Click',
'type' => 'LEFT',
'conditions' => array('User.id = Click.id')
),
array(
'table' => '(' . $this->__buildSubQuerySales() . ')',
'alias' => 'Sale',
'type' => 'LEFT',
'conditions' => array('User.id = Sale.id')
)
);
if (isset($query['sort'], $query['direction'])) {
$query['order'] = array($query['sort'] => $query['direction']);
}
return $query;
}
return $results;
}
Of course this is not safe solution, because it omits PaginatorComponent::validateSort()
.
Sorting by fields that don't exist
From the code in the question - it looks like you're already doing the right thing.
The paginator component validates the sort order if it came from the request:
Validate that the desired sorting can be performed on the $object. Only fields or
virtualFields can be sorted on. The direction param will also be sanitized. Lastly
sort + direction keys will be converted into the model friendly order key.
You can use the whitelist parameter to control which columns/fields are available for sorting.
This helps prevent users from ordering large result sets on un-indexed values.
Passing a whitelist, as shown in the question, should permit skipping this logic but if it doesn't debugging the paginator component will probably identify some kind of mismatch between the name of the $field
, and the values in the $whitelist
.
Being explicit works
Bearing in mind that the call in the question is an admin route - you can forego the "security" of validating the sort parameters and just set the sort order explicitly from the request:
public function admin_index() {
list($from, $to) = $this->_prepareCommonVariables();
//$this->paginate = array('stats'); // <- not the right syntax
$settings = array('findType' => 'stats');
if (isset($this->request->params['named']['sort'])) {
$sortField = $this->request->params['named']['sort'];
$dir = $this->requestparams['named']['direction'];
$settings['order'] = array($sortField => $dir);
}
$this->paginate = $settings;
$this->User->recursive = 0;
$this->set('users', $this->paginate('User',
array(
'Click.created BETWEEN ? AND ?' => array($from, $to),
'Sale.sold BETWEEN ? AND ?' => array($from, $to)
),
array('User.username', 'Click.unique', 'Click.clicks', 'Sale.sales_1', 'Sale.sales_2')
));
}