Web App Development - Systems Architecture - API Building - Security Audits

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
<?php
 
class MyRoute extends Zend_Controller_Router_Route
{
    public function match($path)
    {
        $results = parent::match($path);
        $i = 0;
 
        if ($results === false) { 
            return false;
        }
 
        foreach($this->_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

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

Leave a comment




About this blog

We like to blog about things we're passionate about. We love PHP, MySQL, CouchDB, Linux, Apache - web development standards. We also like writing about building web apps and working with web technology.
You can email us on freedom@echolibre.com

Follow us on Twitter

Eamon Leonard - @EamonLeonard
David Coallier - @DavidCoallier
Helgi Þormar Þorbjörnsson - @h
J.D Fitz.Gerald - @jdfitzgerald
Noah Slater - @nslater
Court Ewing - @courtewing

 

 

 

echolibre limited is registered in Ireland, company number 451576. Directors: Eamon Leonard, J.D Fitz.Gerald. Registered Office: 64 Dame Street, Dublin 2, Ireland.