Thursday, April 12, 2007

CakePHP Routing Explained

I'm currently playing around a bit more with CakePHP. I'm rewriting our CMS system based on the CakePHP framework. It's a great learning exercise because you constantly have to deal with real life problems!

Just now I was wondering about the url structure of the website. The CMS has two different "areas" designed for two different end users. First there is the site itself (displayed to the visitor of the website) and second there is the CMS administration panel. Because I started designing the system from the administration panel's perspective, I soon discovered that thát part of the website was going to be accessed at the root (/). This of course isn't a logical structure, as it would require the pages the visitors visit to have a prefix or something.. So I looked into CakePHP's routing system and came up with some solutions to my problem.

First of all, there is CakePHP's CAKE_ADMIN config setting. This is a routing solution that allows a function called: index() in controller: users to be accessed like this: /admin/users/index. All you need to do is rename the method to: admin_index() and set the CAKE_ADMIN variable to 'admin'. This seemed like a nice solution, but frankly it wasn't. You can still access the controller without the /admin/ prefix, but it will then give out errors (view for admin_index() could not be found). So I went to play a bit more, and found out the following:

  1. You can easily reconstruct Cake's default routing (i.e.: /controller/action/param1/param2/...) by using the following route:
    $Route->connect('/:controller/:action/*', array());
  2. The order in which routes are set matter: if one route is successful, Cake will not bother to look at the rest of the routes.
So I designed my own /admin/ prefix, like so:
$Route->connect('/admin/:controller/:action/*', array());

Then by changing the default route (for '/') to the following line:
$Route->connect('/:page/*', array('controller' => 'posts', 'action' => 'output'));

I can have nice page structures, which are rerouted to /posts/output... Using this structure /posts/output knows which page to load by looking at $this->params['page'] (:page is a custom parameter!). Even better, secondary parameters are still passed (thanks to the trailing /*)!

Since CakePHP uses ordering in the routing, it stops routing after a route was successful. So if you place the /admin/ route above the /:page/* route, administration should be fine!
Also, you might want /admin/ to be accessible without passing on controller/action, etc. (i.e. you would end up at the start page for the administration panel). To do this, include the following route (place it at the top):
$Route->connect('/admin*', array('controller' => 'posts', 'action' => 'index', 'page'));

This will reroute a request to /admin to a default routing location (for me: /posts/index/page)

What I find strange is the fact that the above route doesn't stop Cake from looking on (it will still find the more elabore one; /admin/:controller/:action/*). This is possibly because of the trailing Asterix (*)?

You can of course use these techniques to set up different "areas" for your website (while physically all files are located in the same Cake folder). For instance you could have the following areas:

simply by setting up your routings properly.

Please note the 'standard' routing variables:

Here: :controller sets the controller Cake will use and :action sets the action that will be invoked. Furthermore you can set custom parameters with this:
Now the value for page will be available by using: $this->params['page'].
By the way, other parameters are available in the following array: $this->params['pass']!

I know the post is a bit messy, still I hope it might help some people out! Good luck!


Dustin Weber said...

I have been dealing with the same issues myself.

Good article!

- Dustin Weber

nate said...

Routing in Cake 1.2 is much better. You can assign custom regular expressions to route elements, which makes you matching more exact.

See here:

Quaint said...

Thanks for the comments! Appreciate it!

John said...

Thanks for the post - nicely explained - I've been in a twist with some custom routing this afternoon and this might just do the trick.