Thursday, February 18, 2010

CakePHP Pagination & Custom Routes Issue

Custom Routes are something that make CakePHP very interesting! If you are reading this post you must have experimented with what we call custom routing. The problem might arise when we want to paginate models which use custom routing.

The problems may be many-folded which all simplifies to one simple issue:

UNFORMATTED URL
Say, you want to display url in the following format using custom routes

CakePHP default url structure
http://example.com/categories/index/categoryID

Your WELL FORMATTED URL structure:
http://example.com/category-name-ID

Obviously, your custom route element points to 'index' action of 'categories' controller with two parameters - category-name and 'categoryID'.

Router::connect(

 '/:slug-:id/*',
  array('controller' => 'categories', 'action' => 'index'),
  array(
  // order matters
  'pass' => array('slug','id'),
 'id'=> '[0-9]+'
  )
  );

This works perfectly for URLs like
http://example.com/my-first-category-ID1
http://example.com/my-second-category-ID2

But it may cause problem when you try to paginate

Cake will not pick  your params (here 'slug' and 'id') passed through url if you do not force Cake to do so while paginating.  

To fix this issue, you MAY use:

 $paginator->options(array('url' => $this->passedArgs));  
or, as I have told here earlier following CakePHP book.

But this will format your paginated urls like
http://example.com/categories/index/param1/param2/page:xxxx

You can obviously browse pages with the above url. But it does not look decent. So, your entire effort with custom routing might just not work. 

You may still get a bad URL.

DO NOT WORRY!

While defining paginator options in your view file, follow religiously $html->link() structure. For an example I have put forward the structure of  $paginator->options() below in a view file:

file:// /app/views/categories/index.php

$paginator->options(array('url'=> array(
'controller' => 'categories', 
  'action' => 'index',
'slug'=>$this->params['slug']),
'id'=>$this->params['id'])
  ));

Now Cake will make well formatted URL automatically, and your paginated url should look like

http://example.com/category-name-id1/page:2
http://example.com/category-name-id1/page:3

Done!
I hope it helps someone.
Does it?

Please Note
I have marked asterisk symbol (*) with RED color while talking about custom route elements. This (*) MUST be there for pagination to work properly. 
Good night.

9 comments:

Unknown said...

thanks for this information!

Siddhartha said...

you are welcome.

Unknown said...

thanks ! for your help.

I posted a same/inspired solution on my blog in french.

french inspired version

Siddhartha said...

Checked your post. It is really useful.

Unknown said...

Just to note, I came accross the same problem earlier, but this was using a route in a plugin, I found the route would take on the plugin name before the controller name, or even the name you override the contoller with. To get past this just add 'plugin' => null to the paginator options

Siddhartha said...

Thanks Peter. Yes. You are quite right.

Manu said...

Thanks a lot for this article, I had trouble with my pagination routes and passing the url manually worked perfectly in the end :)

Siddhartha said...

Thanks Manu. I am glad to know that it helped you.

Max said...

Hi man. Thank you for the article. Could you please help me because it does not work for me.

My routes code is:
Router::connect('/:type-:id/:slug', array('controller' => 'Registers', 'action' => 'view'),array('pass' => array('type', 'id', 'slug')));

My paginator option code is:
$this->Paginator->options(
array('update'=>'#CustomerPaging',
'url'=> array(
'controller' => 'registers',
'action' => 'view',
'slug'=>$this->params['slug']),
'indicator' => '#LoadingDiv'));

The result url is: /registers/view/page:value/slug:value

So cake does not formatting the url automatically. Any help will be appreciated.