Christer Edvartsen
Written by Christer Edvartsen
Published 2011-11-08

Fat Models, Chubby Routes, Super-Skinny Controllers

Some of you have probably heard the “Fat models, skinny controllers” mantra with regards to developing MVC applications. Recently we figured out that the routes (not part of MVC, but a crucial part to the application nonetheless) could take some weight off the controllers. Since we use Zend Framework (ZF) at VG I will use ZF when describing this solution.

You need to be familiar with some ZF basics to follow along with all the code presented in this post, but fear not, the idea itself can easily be transferred to other frameworks/languages.

Entry image


All source code used in the post is available at GitHub.

The application I will use for this example has three controllers: Index, Article and Error. I will only focus on the Article controller in this post.

The article controller is, as you probably have guessed, responsible for letting the users of the application view articles. The application serves articles on URLs that matches the following pattern:

/article/<id>-<title>

In ZF we will have to create a route that matches the above pattern. This can be accomplished by using a Zend_Controller_Router_Route_Regex route and adding it to the router in the bootstrap process:

If someone requests /article/123-some-title the route above will match, and the dispatcher will tell the article controller to execute the index action. There is no validation mechanism at work yet, so the article controller will have to talk to some database and check if there is an article with id equal to 123 available and if the requested title (some-title) is correct.

This is probably the most typical chain of events for such an application.

There is a lot going on in ZF after the route matches the request and before the controller gets to validate the input. Wouldn’t it be great if the chain of events would stop in the route if the client requests an article that does not exist? And wouldn’t it be equally great if we had a way of automatically issuing a 301 redirect if the client requests an URL with the wrong title?

To do this we need to beef up our route. First, lets extend the Zend_Controller_Router_Route_Regex route and provide some extra logic to the match method:

The way ZF does its request matching is that it iterates over all routes added to the router and executes the match() method on each route. The first route returning something other than false will be the one that the router uses. As you can see above we are letting the parent class do the actual matching based on the regular expression mentioned earlier in the post. If the parent method returns a valid match, we want to extract the parts of the URL that is interesting for us, namely the id of the article. At this point, we don’t really care about the title specified in the request. This will be handled in a plugin that will be executed after the routing is finished.

If we somehow ended up with an id that is not present in the “database” we will throw a Zend_Controller_Router_Exception exception with a fitting message and a status code of 404. The error controller will use this information when presenting an error page for the user.

If the article exists we fetch it from the backend and add it to the $match array. ZF’s router will populate the request instance with all elements in $match, and these elements can be easily fetched by code that can access the request instance (like for instance controller actions and some plugins).

Now we will add support for redirecting the client if an incorrect title is specified in the URL. This is accomplished using a plugin that is triggered after the routing is finished.

The plugin can look like this:

As you can see from the class above we are implementing the routeShutdown() method that is triggered after the routing is finished. In this method we fetch the current route (the route that matched the current request), and then we assemble the correct URL using the parameters found in the request. These parameters are the same as the ones we put in our $match array in the route class above.

After assembling the route we check that the result matches the currently accessed path. If not, we fetch the response instance and issues a 301 redirect that is sent back to the client. The client will then make a new request to the correct URL, and during the next request the Redirector plugin won’t need to do a redirect.

Now it’s finally time for the article controller to shine. The controller can look like this:

All the controller now needs to do is fetch the article from the request instance (that we indirectly put there by putting the article object in the $match array in our route class). There is no longer any need to validate the request input in the controller since this has already been done in the route, that is a more logical place to do that sort of validation in the first place.

The code available in the repository over at GitHub includes a tiny application that uses the code presented in this post. Feel free to play around with it, and don’t forget to comment if you have something to say about this solution.

Written by Christer Edvartsen
Published 2011-11-08