CakePHP: paginating with search logic in a model

2019-08-19 14:52发布

问题:

I'm having trouble paginating a search result. My setup is as follow.

I have a search form at myapp.com/searches/products with view (has search form) .../app/views/searches/products.ctp. I am employing a Searches controller that uses the Product Model for this search query. Product has action search() with the search logic ($this->find(...)). The search result is displayed below the form in the view.

How do I go about performing something similar to $this->paginate(), which is normally done in the controller? Additionally, I'd like to know if there is something wrong with my setup, particularly with the view that includes both the form and the search result.

回答1:

One way to keep your search logic in the model, and still paginate in the controller is to do this:

Explanation:

Instead of returning the actual results from the model, just return any/all find options, then do the pagination like you normally would. For some examples, like this simple one below, it might seem overkill, but it leaves room for adding a lot more options to your find() like contain, order, group, joins, conditions... etc etc. And stays more in-line with the "Fat Models, Skinny Controllers" mantra.

It's also nice to set up your find()s with options like this so it's easily re-usable throughout your site - just pass it different options and you're good to go.

Code:

/* CONTROLLER
*/
$opts = array('paginate' => true, 'limit'=>20);
$paginateOptions = $this->Event->getEvents($opts);
$this->paginate = $paginateOptions;
$data = $this->paginate('Event');

/* MODEL
*/
public function getProducts($opts = null) {

    $params = array();

    //limit
    $params['limit'] = 50; //default
    if(!empty($opts['limit'])) $params['limit'] = $opts['limit'];

    //paginate option
    $paginate = false;
    if(isset($opts['paginate'])) {
        if($opts['paginate']) $paginate = true;
    }

    //either return the options just created (paginate)
    if($paginate) {
        return $qOpts;

    //or return the events data
    } else {
        $data = $this->find('all', $qOpts);
        return $data;
    }
}

There are ways to write this a bit slimmer / less lines of code - but I like writing it like this so it's quickly understandable.

(There doesn't appear to be anything wrong with your overall structure.)



回答2:

I usually use to store the search parameters in the session and handle everything inside the controller action.

function indexbystatus() {
    $this->set('title_for_layout','List Assets by Status');
    $this->Session->write('sender',array('controller'=>'assets','action'=>'indexbystatus'));
    $searchkey=$this->Session->read('Searchkey.status');
    $conditions='';
    if($searchkey) {
        $conditions=array('Asset.status_id'=>$searchkey);
    }
    if(!empty($this->data)) {
        // if user has sent anything by the searchform set conditions and
        // store it to the session but if it is empty we delete the stored
        // searchkey (this way we can reset the search)
        if($this->data['Asset']['status_id']!='') {
            $conditions=array('Asset.status_id'=>$this->data['Asset']['status_id']);
            $this->Session->write('Searchkey.status',$this->data['Asset']['status_id']);
        } else {
            $this->Session->delete('Searchkey.status');
            $conditions=null;
        }
    } else if($searchkey) {
        // if no data from the searchform we set the stored one
        // from the session if any
        $this->data['Asset']['status_id']=$searchkey;
    }
    $this->paginate=array(
                        'limit'=>25,
                        'order'=>array('Asset.status_id'=>'asc'),
                        'conditions'=>$conditions,
                        );
    $this->set('assets',$this->paginate());
    $statuses=$this->Asset->Status->find('list');
    $this->set('statuses',$statuses);
}

I just like it more to handle it in the controller action not in the model so I could have different solutions and logic by actions.