Order by doesn't work with custom model find w

2019-07-09 23:45发布

问题:

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.

回答1:

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().



回答2:

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