Search

Custom Zend Framework Router

Saturday, June 30. 2007

Zend Framework One could tweak Zend Framework's Router_Route to meet almost all your routing needs. But what if you want something beyond what that package can offer? You can make your routing dreams come true with making your own custom router, all you need to do is implement Zend_Controller_Router_Route_Interface (that's a mouth full).

Case example, I needed a website that could have an arbitrary amount of hierarchal categories. I wanted my URI path to reflect the full path to that category. For example:

http://www.example.org/category/clothing/hats/dress/fedora

This URL would represent the category controller. Each category is a child of the category to the left of it, clothing being the top most parent.

For this particular router I ignored the action component. The category controller will only have an index action, for all that is needed is to view the contents of the category. Administration of the categories happen in a completely different app, so I really don't need the usual scaffolding behavior. I just need the Route to route to the correct controller and then put the rest of the URL into a flat array that I can retrieve with _getParam(). Looking at the example below however, it would be very easy to figure out how to make action routing work right :)

Zend_Controller_Router_Route_Interface requires to implement three methods: match(), assemble(), and getInstance(). The match() method is what does all the magic, it parses the url, decides if it's the right router for this url, and then passes an array of info to the dispatcher. The assemble() method is used to reconstruct an URL given an array of data. This is mainly used by some helpers like Zend_View_Helper_Url. I won't show how to implement this method since it's slightly beyond the scope of this article. The assemble() is not necessary for the Router to work. Since the Interface requires it, I'll put it in the class as a dummy method. You can play with Url Helper on your own to get assemble() to return what is needed for this Helper. The getInstance() method is simply a factory method to create instances of itself from Zend_Config.

 
<?php
class MyApp_CatRoute implements Zend_Controller_Router_Route_Interface
{
	public $defaults = array();
        public $name = null;
 
        //this class sets the defaults and the name of the controller
        public function __construct($name, $default = array())
        {
            	if(!empty($default)){
            		$this->defaults = $default;
            	} else {
            		$this->defaults = array(
                        'controller' => 'category',
                        'action' => 'index'
                    );
            	}
                $this->name = $name;
        }
 
        public function match($path)
        {
            //splits the URL into an array
            if (preg_match_all('#/([^/]*)#', $path, $matches)) {
                //gets the array from the preg_match
                $segments = $matches[1];
                /*pops off the first element and checks if 
                 *this is the right router, if not, return   
                 *false, Zend framework will continue looking 
                 *for the right router
                 */
                $category = array_shift($segments);
                if ($category != $this->defaults['controller']){
                	return false;
                }
                //creates an array, the array with all the categories are indexed by 'cats'
                $return = array(
                    'category' => $category,
                    'cats' => $segments 
                );
                //merges the array above with the defaults and returns it.
                $return = array_merge($return,$this->defaults);
                return $return;
            }
            return false;
        }
 
        public function assemble($data = array())
        {
            return $route;
        }
 
        public static function getInstance(Zend_Config $config){
            $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
            return new self($config->route, $defs);
        }
}
?>
 
 

Basically the idea is to have match() return an array with atleast the 'controller' and 'action' indexes set. If your using a modular layout you would also need a 'module' index in the array. Any other index you can set is completely up to you, and those indexes are accessible with _getParam() in the controller. Here is how I set up this Route in the bootstrap:

 
require_once('MyApp/CatRoute.php');
$rwRouter->addRoute('cat', new MyApp_CatRoute('category',array('controller' => 'category', 'action' => 'index')));
 

Finally one can grab the array of categories easily in your action:

 
$cats = $this->_getParam('cats', array());
 

If you have any questions you can find me on irc on freenode.com in #zftalk. You can also find me on the ZF General Mailing list. Have Fun!


delicious logo digg logo technorati logo furl logo stumble upon logo feed logo

Comments

Outstanding. I am definitely going to look into this. I need to port through various platforms, each having their own set of m/c/a's, along with schema and location. I think this will help a lot.

Thanks.
#15 Posted By: Jonathan Kushneforr | March 10, 2008 8:19 AM | reply
Thanks for this. I am new to ZF and this article really helped me out.

I used a similar Route to pass all requests onto index/index with an extra page param so I could easily and dynamically pull pages out of a database.
#6 Posted By: Daniel Skinner | October 1, 2007 10:43 AM | reply

Add Comment

  E-Mail address will not be displayed.
Cookies must be enabled to post a comment
goawai
 
 
 *Required Field