Using the Zend Dispatcher to simulate URL rewriting

I haven’t post anything in quite a while, mostly due to the large amount of work I’ve been juggleing lately and because I did not have the energy to write even the stupidest little thing.

Anyway, I decided to come out from my blogging hiatus and share something that I stumble upon during the course of one of the projects I’ve been working this past weeks.

A few days ago while working on a project in which my team and I are using the Zend Framework (which I hate and not-love-but-like at the same time), getting close to our release date I noticed that we ended up with having some funky URLs for accessing some parts of the application, I mean there’s nothing bad of having an URLĀ  in the form of /module/controller/action/param/value, which is OK at some point, however somehow some of the most relevant modules of the application ended up with having the logic on the module’s index controller, and on top of that on the index action of the controller, so some of the web pages that are going to receive the most of the traffic ended up having an access URL like this: http://mysupercoolsite.com/item/index/index/90 - a big WTF from my side, index/index ??! . Ok, somethings were not well planned at the outset nor given the necessary attention- my bad - although this “issue” does not affect anything whatsoever, I just think it doesn’t look good!!! and perhaps it says that you did not do a good job designing the application in the first place (probably I’m just overreacting. I’m pretty sure though, that the client would’ve told us - “Hey! I want beautiful and meaningful URLs! what the fuck are those index/index for? Get them out of there ASAP!”- We needed some re-factoring and changes, but being so close to the “D” day that was not an option. Doing some tricks with mod_rewrite neither it was.

Enter the Dispatcher.

Ok, before I move on, let me tell you that I don’t consider myself a Zend Framework expert, I have used it on some projects but never got too deep into it, so perhaps there’s a better and simpler option that I might have overlooked.

So, I had to figure out how to catch the request before dispatching it and be able to modify some of its values on the fly, in order to translate a beautiful URL to the bizarre one we had in our application and still be transparent to the end user. I found some articles that discussed this stuff at some extent, but with no actual solution - I did not find in the ZF docs either a straight-forward example or explanation on how to do this when you use modules.

I decided to stop fucking around and get to the point - I knew that the Dispatcher was the solution, because its the one that takes the request, gets the module/controller name and process it (instantiates whatever controller and calls its action). By default you’re application is tied to the Zend_Controller_Dispatcher_Standard, so what I thought is “well, if I can get the module, controller, and action names, and the param/values coming in the request before the dispatch process, perhaps just adding a method that does some sort of pseudo-rewriting shit will do the trick”.

I started by extending Zend_Controller_Dispatcher_Standard instead of Zend_Controller_Dispatcher_Abstract, why? no real reason, why not? The idea was to extend it by adding a method that lets you add, what I call, translations (or “pseudo” rewrite rules), and run those translations against a given request that the dispatcher gets, and if it does match some rules well modify it and until then dispatch it.

First step,

class MyCustom_Dispatcher extends Zend_Controller_Dispatcher_Standard{
 
}

Ok, we are doing great so far, then I set a private variable to hold all translations (or rules if you will) added to the dispatcher for consideration. And then my method to add translations to it

public function addTranslation($params)
{
if ( !is_array($params) )
{
throw new Exception('Invalid dispatcher translation');
return;
}
 
if ( null === $params['target'] || ( null === $params['module'] && null === $params['controller']) )
{
throw new Exception('Invalid dispatcher translation');
return;
}
 
if ( null === $this->_translations ) $this->_translations = array();
 
array_push($this->_translations, $params);
}

Ok, I have my method to add translations, Yay! now what? Well, next the method to run those translations against an incoming request (some code omitted for the sake of brevity), here is an example of this method.

private function runTranslations(Zend_Controller_Request_Abstract &$request, $m, $c, $a)
{
 
	if ( $this->_translations !== null )
	{
		foreach( $this->_translations as $trans )
		{
			if ( $trans['module'] !== null )
			{
				if ( $trans['controller'] !== null && $trans['controller'] == $c && $trans['module'] == $m)
				{
					if ( $trans['target']['module'] !== null ) $request->setModuleName($trans['target']['module']);
					if ( $trans['target']['controller'] !== null ) $request->setControllerName($trans['target']['controller']);
					if ( $trans['target']['action'] !== null ) $request->setActionName($trans['target']['action']);
 
					if ( $trans['params'] !== null )
					{
						foreach($trans['params'] as $key=>$value)
						{
							$request->setParam($key, $request->getParam($value));
						}
					}
 
					return;
				}
			}
		}
	}
	return;
}

Ok, now I have my method that will run the translation rule against an incoming request, and if necessary make some adjustments to it. But before we move on, where does this “translations” are evaluated ? Well, we have to override the dispatch method to include some custom actions.

public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
{
	$moduleName = $request->getModuleName();
	$controllerName = $request->getControllerName();
	$actionName = $request->getActionName();
 
	$this->runTranslations($request, $moduleName, $controllerName, $actionName);
 
	parent::dispatch($request, $response);
}

Basically, before dispatching our actions we evaluate our request against the predefined rules our dispatcher has - in simpler words we are just taking a request and modifying/overwritting its controller, module, action, params that it target to. Ok, I think that’s it for our custom dispatcher. Now, where on earth do I tell my application to dump the stupid standard dispatcher and use my cool one ? That’s in our application’s bootstrap class. If you are already overriding the constructor of your dispatcher fine, if not you’ll need to in order to change the front controller dispatcher, and at the same time define your translations or rewriting rules.

public function __construct($application)
{
	parent::__construct($application);
 
	$customDispatcher = new MyCustom_Dispatcher();
 
	$params = array(
		'module' => 'default',
		'controller' => 'item',
		'target' => array('module'=> 'items', 'controller' => 'index', 'action' => 'index'),
		'params' => array('itemid' => 'action')
	);
 
	$customDispatcher->addTranslation($params);
 
	$front = Zend_Controller_Front::getInstance();
	$front->setDispatcher($customDispatcher);
}

Some explanation of the params set in the code block above. The way translation works, is that you have to tell in which way the request is coming, and what it translates to, so the application knows what to do with it. By the code above, I’m saying to the dispatcher, please add a rule that whenever a request comes to “default” module and the “item” controller, you really want to target the “index” action at the “index” controller of the “items” module. There’s no “item” controller within the “default” module. Also, since our “real” module-controller-action expects a parameter called “itemid”, you notify the dispatcher that the request will contain this value as its action. For example,

http://aaa.com/item/10 = http://aaa.com/items/index/index/itemid/10

Since the framework sees item/10 as controller “item”, action “10″, that’s why you have to tell the dispatcher that you parameter value is represented as the “action”.

There you have, now we can tell our client that he’ll have beautiful URLs. The end user never gets to see the real identity of our bizarre URL. This is a very particular use case, that it was derived from improper design in the first place, that worked for us, it does not mean that is the best approach or that it will work for everybody, its just a sort of hack/experiment that I just wanted to share because I’m a cool guy.

Conclusion: The Zend Dispatcher is the entry point where you can do whatever you want to do with any incoming request before send it to execution. Cheers! :D

Mario.


2 Responses to “Using the Zend Dispatcher to simulate URL rewriting”

Leave a Reply