There’s a quote that you often see in the #cakephp channel on irc.freenode.net and it goes something like, “If something is hard in CakePHP, then you’re doing it wrong”. This is proven to me over and over the more comfortable I get with CakePHP. One of the most annoying problems I had as a Cake newbie was a pretty common scenario:
I’m using Cake’s Paginator component and helper to paginate results from my profiles table. Here, sweet Jesus, is a perfect example of why I love Cake! Pagination used to be such a pain in the ass! Now that I’ve got that working I need to set up some forms so that the results can be filtered. So I write up my filter criteria form using the Form helper (*squeal* *gush*). Say I want a filter on the ‘city’ and ‘name’ fields. Also, because I want to be able to bookmark these customized results, I’m going to use the get method for my form. I set my controller action to parse the form input as usual ($this->data).
Everything looks good to me at this point so I submit the form. Ok, it works. But the URL looks like /profiles/search?name=joe&city=toronto, which looks kinda ugly. Oh, and crap, now my pagination is broken. At this point I come up with various hacks that sorta work, but they’re all terrible. Maybe I’ll rewrite some portions of the Paginator helper, or create my own version because holy shit how did these Cake guys make such a glaring mistake! The stupid Paginator helper uses named parameters but I need to submit my form via get so that I can bookmark the results! Come to think of it, what the hell are these named parameters good for in the first place? Much frustration ensues.
And then… I’m enlightened. I can’t remember by who. But thanks! I was going about it all wrong. And because I want some good karma I’m going to pass on the knowledge and hopefully save you some trouble.
The trick is to use a concept/design pattern called Post/Redirect/Get. Basically what you need to do is change your search form’s method to post, have your controller action do a little processing on the form submission and then redirect to a URL using named parameters. You’ll end up with a pretty URL and it will play nice with your paginator links. Here’s how to do it:
Like I mentioned before, change your form to post in your view. In your controller action, do something like this
// If we have a form submission...
if( $this->data ) {
// Set up the URL that we will redirect to
$url = array('controller' => 'profiles', 'action' => 'search');
// If we have parameters, loop through and URL encode them
if( is_array($this->data['Profile']) ) {
foreach($this->data['Profile'] as &$profile) {
$profile = urlencode($profile);
}
}
// Merge our URL-encoded data with the URL parameters set above...
$params = array_merge($url, $this->data['Profile']);
// Do the (magical) redirect
$this->redirect($params);
}
That will handle the redirection. But what about parsing the named parameters now? That’s cake too:
// Set up filter conditions
$conditions = array();
if( !empty($this->params['named']['email']) ) {
$conditions['Profile.email LIKE'] = '%'.$this->params['named']['email'].'%';
}
if( !empty($this->params['named']['city']) ) {
$conditions['Profile.city LIKE'] = '%'.$this->params['named']['city'].'%';
}
// Repeat blocks like the above for all your parameters...
$this->paginate = array(
'conditions' => $conditions,
'contain' => '' // You ARE using Containable right?
);
// Set things up so the form values are set when the page reloads...
$this->data['Profile'] = $this->params['named'];
// Get our data...
$this->set('data', $this->paginate('Profile'));
I don’t know about you, but this idea is not what I would’ve called obvious at the time. I’d never come across any documentation that said the redirect function at the end of that snippet would turn all the elements at the end of my $params array into named parameters, but that’s exactly what it does. Try it, and you’ll see ;).
Now as long as your view is set up correctly, your form will work in complete harmony with your paginator links!
After putting all of this together I was really happy with myself, but not for long. Almost immediately after this discovery I tried applying the same concept to a different form. This form was slightly different though. Imagine a criteria, say a few checkboxes that specify types to show in the search. A user can check any combination of the boxes. Normally this would be handled almost entirely behind the scenes by PHP, which would combine multiple selections into an array within the $_POST or $_GET variable (and $this->data as a result). But because we’re using named parameters this isn’t possible and guess what, named parameters are unique. Damn those CakePHP devs and their ignorance for not thinking ahead on this one, right? Wrong.
In my opinion, the elegant solution here would be to use PHP’s serialize() and base64_encode() functions to turn our posted data into something we can stick into the URL easily. Modify your controller action to look something like the following:
if( $this->data ) {
$url = array('controller' => 'profiles', 'action' => 'search');
if( is_array($this->data['Profile']) ) {
foreach($this->data['Profile'] as &$profile) {
// If we're dealing with an array of selections, serialize and base64_encode it into a URL-safe string
if( is_array($profile) ) {
$profile = base64_encode(serialize($profile));
} else {
$profile = urlencode($profile);
}
}
}
$params = array_merge($url, $this->data['Profile']);
$this->redirect($params);
}
Now our URL will look like: /profiles/search/options:YToyOntpOjA7czoxOiIxIjtpOjE7czoxOiIzIjt9/name:joe/city:toronto.
In your controller action all you have to do is modify your parsing of criteria to unserialize and base64_decode that options parameter:
if( !empty($this->params['named']['options']) ) {
// base64_decode and unserialize, and also assign it to $this->data so that the boxes are checked properly when we redisplay the form
$this->params['named']['options'] = $this->data['Profile']['options'] = unserialize(base64_decode($this->params['named']['options']));
// If unserialize returned a valid array...
if( is_array($this->params['named']['options']) ) {
if( count($this->params['named']['options']) > 0 ) {
$conditions['Profile.options'] = $this->params['named']['options'];
}
}
}
You might have to do a little bit more to parse your criteria, but that’s the general idea.
Finally, your search form and pagination are working together flawlessly, and you have pretty URLs too!

Exactly what i was looking for.. thx a bunch…u can have a share of my karma..
Glad to help! ;)
finally!!!!!… thx so much
Pingback: Cakephp search filters with pagination | Ron Heywood