Customising Zend Framework Routing

Posted by J.D. in PHP, Zend Framework
Monday, March 9th, 2009 at 14:08
I wanted to write a post that shows a few different ways to customise Zend Frameworks routing when you’re using their MVC implementation. Most of this is covered in the documentation, but it can be a little difficult to dig out.

The standard routing setup of Zend matches URLs like these:

1
2
www.example.com/module/controller/action/var1/value1/var2/value2
www.example.com/controller/action/var1/value1/var2/value2
You set your controller directories in your bootstrap with something like:

1
2
$front = Zend_Controller_Front::getInstance();
$front->setControllerDirectory(array(‘default’=>’../controllers’, ‘bar’=>’../modules/bar’, ‘foo’=>’../modules/foo’));
For the first component of the path Zend will first look for a matching module, if none is found it will look for a matching controller. The module name ‘default’ is important here; it represents (surprisingly) the default controllers. So these are the ones when there’s no matching module in the URL.

Now on to the magic…
The original reason I started delving into all this was because I wanted to have a url without specifying an action, yet still have variables. I also wanted this broken into modules like so:

1
www.example.com/foo/eek/var1/value1/var2/value2
… to resolve to the module ‘foo’, the controller ‘eek’, and pass the parameters var1, var2. To do this I set up the following route:

1
2
3
4
5
6
$route = new Zend_Controller_Router_Route(
‘:module/:controller/*’,
array(‘action’=>’index’)
);
$router = $front->getRouter();
$router->addRoute(‘modules’, $route);
The router will try to match a url against the pattern specified as the first argument to the constructor. It uses a simple syntax where any word preceded by a ‘:’ is a variable, and a * will match anything that follows. The variables module, controller and action are special names. They refer to where the query should be routed to. Hence the pattern above will match /foo/bar/a/b to module = foo, controller = bar, and the parameter a = b. The action of index is specified in the second parameter. This array can be used to give defaults to any parameters you expect, or module/controller/action.

When adding a route the first parameter is just a label, it doesn’t have any influence. If you specify the same label twice the second will overwrite the first. So if you want to overwrite the default route then use the label ‘default’. As you add more routes they’re matched in the reverse order to which they’re added.

Finally I wanted to override the default way parameters are passed. Given the URL:

1
www.example.com/foo/eek/a/b/c/d
… the parameters passed to the controller are (’a’=>’b’,’c’=>’d’). I wanted a variable number of parameters to be passed in, but for none of them to be named. It should result in a parameter array of (1=>’a’,2=>’b’,3=>’c’,4=>’d’). To do this you can extend the Zend_Controller_Router_Route class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_wildcardData as $key=>$value) {
unset($results[$key]);
$vars[++$i] = $key;
$vars[++$i] = $value;
}

$results += $vars;
return $results;
}
}
This solution is a bit of a hack*, but it works 😉

Each subclass of Zend_Controller_Router_Abstract has a method called match. This method is passed the path, and returns either false if the route doesn’t match the path or an array of results if it does. The array will contain the module, controller and action; as well as the parameters to be passed to the action.

The above code uses the standard routers match function, then doctors the resultant array so that it’s the way I want it. _wildcardData is an array that’s built up during the parent’s match method, containing all the parameters.

To use it, just treat it as you would a normal route:

1
2
$route = new MyRoute(‘:module/:controller/*’,array(‘action’=>’index’));
$router->addRoute(‘modules’,$route);
Interesting stuff can also be done with Zend_Controller_Router_Route_Regex, but I don’t think you can make it have a variable number of parameters.

*Please God no one bring up the call super antipattern…

You can leave a response, or trackback from your own site.

Comments (One Response)
Kieran Metcalfe
July 16th, 2009 at 09:19
J.D.

You’ve just saved my life! Thanks for posting this – it was exactly what I needed for a site I’m redeveloping in ZF.

I had URLs in the format http://host.com/area/title

which needed to map to areaController, index action, with title as a parameter. Using a variation of the above, that’s ticking over nicely.

Many thanks