Frameworks, PHP, Software Architecture

Improving the performance of Zend_Controller

Zend_Controller_Router_Rewrite is the standard framework router. It takes a URI endpoint and decomposes it into parameters to determine which module, controller, and action of that controller should receive the request. Routing occurs only once, when the request is initially received and before the first controller is dispatched.

To use the router you have to instantiate it, add some user defined routes and inject it into the controller:

$routes['blog'] = new Zend_Controller_Router_Route(
		'year'       => date('Y'),
		'month'      => date('m'),
		'day'        => date('d'),
		'module'     => 'blog',
		'controller' => 'index',
		'action'     => 'archive'
		'year'  => '\d+',
		'month' => '\d+',
		'day'   => '\d+',
		'title' => '([0-9a-zA-Z-]+)',

$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();

Front Controller dispatch mechanism:

o-- Zend_Controller_Front::dispatch()
--> Zend_Controller_Front::setResponse()
--> Zend_Controller_Front::getRouter()
--> Zend_Controller_Router_Rewrite::setParams()
--> Zend_Controller_Router_Rewrite::route()

Zend_Controller_Front gets an instance of Zend_Controller_Router_Rewrite, routes the request, iterates through all the provided routes and matches its definitions to the current request URI.

The problem

The router has to load all the objects into memory, iterate through all the routes and perform regular expressions until it finds a positive match. This means that the more routes you add, the longer the router will take to process them. The problem becomes more critical when using XML or INI configuration files and a site increases in complexity and attracts more traffic.

The Solution

One of the solutions is to split the routes file into different .php or .ini files. For example, instead of loading a single file:

$frontController = Zend_Controller_Front::getInstance();
$config = new Zend_Config_Ini(
$router = $frontController->getRouter();
$router->addConfig($config, 'routes');

You can use the My_Controller_Router_Rewrite class to set the path to the directory where all the configuration files are located:

$frontController = Zend_Controller_Front::getInstance();
$router = new My_Controller_Router_Rewrite();
	array('production', 'routes')

This allows the router to map the first segment of the URL path to a filename. For example, given the following URI:

The router will load the blog.ini file:

|-- bootstrap.php
|-- config
|   `-- routes
|       |-- blog.ini
|       |-- eshop.ini
|       |-- forum.ini
|       `-- gallery.ini
|-- layouts
|   `-- main.phtml
`-- modules
    |-- blog
        |-- controllers
        |   `-- IndexController.php
        |-- models
        `-- views

Test Result Data

Zend_Controller_Router_Rewrite (4 routes)

Server Hostname:
Document Path:           /blog/2008/07/14/test
Requests per second 1:   88.24 [#/sec]
Requests per second 2:   89.32 [#/sec]

Zend_Controller_Router_Rewrite (16 routes)

Server Hostname:
Document Path:           /blog/2008/07/14/test
Requests per second 1:   64.45 [#/sec]
Requests per second 2:   64.61 [#/sec]

Zend_Controller_Router_Rewrite (20 routes)

Server Hostname:
Document Path:           /blog/2008/07/14/test
Requests per second 1:   59.17 [#/sec]
Requests per second 2:   59.03 [#/sec]

Now, lets try splitting the config.ini file into separate configuration files:

MyRouter extends Zend_Controller_Router_Rewrite (4 routes)

Server Hostname:
Document Path:           /blog/2008/07/14/test
Requests per second 1:   89.47 [#/sec]
Requests per second 2:   89.32 [#/sec]

MyRouter extends Zend_Controller_Router_Rewrite (40 routes)

Server Hostname:
Document Path:           /blog/2008/07/14/test
Requests per second 1:   89.62 [#/sec]
Requests per second 2:   89.41 [#/sec]


27 thoughts on “Improving the performance of Zend_Controller

  1. If I extend Zend_Controller_Router_Rewrite:

    ab -kc 80 -t 60

    4 routes: 89.47 [#/sec]
    20 routes: 89.32 [#/sec]
    80 routes: 89.62 [#/sec]

    Because I’m using multiple configuration files, the total amount of routes does not affect the performance of the routing process when the request is routed to the blog module.

  2. What isn’t clear is what position the blog route has in your route stack. Those figures make me think it’s the first route checked that matches. What’s the worst case scenario, where the last of 20 routes matches and all 20 ini files have to be loaded?

  3. @Eran: I personally don’t like having extraneous words in my url just so my routing can be simple. If I think of /book/view/id/10, the words id and view add no value and just make the url less guessable. I therefore prefer to explicitly map out every url on my site, exposing no information about controllers/actions to the user.

    Thanks for the article, Federico. Gonna hook this up right away :)

  4. Nathan: I think what you’re missing here is that the first part of the URL (the “/blog” part) is actually what defines which ini file has to be loaded, so you don’t need to load all 20 files.

  5. Hi Nathan,

    If you use Zend_Controller_Router_Rewrite, you have to add 10, 20 or 40 routes to the config.ini file. And the router has to iterate through 1/40 routes until it finds a positive match.

    But, if you split the file:

    blog.ini (5 routes)
    shop.ini (5 routes)
    gallery.ini (5 routes)
    forum.ini (5 routes)

    The router always iterates through 1/5 routes.

    If you have 20 modules, and each module has 3 to 5 routes, the order of the routes or the popularity of the module (or URI) is irrelevant.

  6. @Eran

    Why do you need so many custom routes?

    For security and maintainability reasons. Using magic numbers to retrieve parameters from the request object can lead to a maintenance nightmare. I’ve seen this done many many times, for example: getParam(1)

  7. I am wondering how easy this would be to modify to have it look inside the module directory for say a routes.ini file that way you can keep the module all centralized.

    I’ll check it out to see what i come up with.

  8. @Jon: Yes, another advantage of doing it this way is that you can create self-contained modules:

    |-- controllers/
    |-- i18n/
    | `-- en/
    |-- models/
    |-- properties/
    | |-- BlogConfig.php
    | |-- BlogRoutes.php
    | `-- BlogDependencies.php
    `-- views/

    You can read more about this here.

  9. Django does something similar to this. Each Django app can have its own As said before it makes the modules more self-contained.

  10. True. Django’s routing process is much faster than Zend’s. Of course, it compiles the regular expression, but the design is more flexible and scales better.

  11. Pingback:
  12. This is very similar to the method I am working on albeit with a different motivation; I am looking to modularise the routes, as opposed to improve performance.

    A problem I have encountered however, is that because only the routes matching the first section of the URL are loaded, reversing routes to generate URLs for other sections doesn’t seem possible.

    eg. You can’t generate a URL for something in gallery/… from within the blog because the gallery routes don’t exist.

    Of course you can write the URL manually, but that negates what I see as one of the more useful parts of the routes.

  13. How many routes do you expect there to be for a large website? If that’s 40 the current implementation may not be the fastest but should be fast enough to handle matching each of those.

  14. Hmm, that’s a tricky one, considering that a module can have n amount of routes, and an application n amount of modules.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s