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(
'blog/:year/:month/:day/:title/*',
array(
'year' => date('Y'),
'month' => date('m'),
'day' => date('d'),
'module' => 'blog',
'controller' => 'index',
'action' => 'archive'
),
array(
'year' => '\d+',
'month' => '\d+',
'day' => '\d+',
'title' => '([0-9a-zA-Z-]+)',
)
);
$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();
$router->addRoutes($routes);
$frontController->dispatch();
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(); $frontController->addModuleDirectory( '../application/modules' ); $config = new Zend_Config_Ini( '../application/config/routes/config.ini', 'production' ); $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(); $frontController->addModuleDirectory( '../application/modules' ); $router = new My_Controller_Router_Rewrite(); $frontController->setRouter($router); $router->setRoutesDirectory( '../application/config/routes', 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:
http://www.zend-test.com/blog/2008/07/14/test
The router will load the blog.ini file:
application
|-- 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: www.zend-test.com 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: www.zend-test.com 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: www.zend-test.com 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: www.zend-test.com 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: www.zend-test.com Document Path: /blog/2008/07/14/test Requests per second 1: 89.62 [#/sec] Requests per second 2: 89.41 [#/sec]
How did the performance differ with your solutions?
Dave Marshall
July 15, 2008 at 8:05 am
If I extend Zend_Controller_Router_Rewrite:
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.
phpimpact
July 15, 2008 at 8:35 am
Why do you need so many custom routes? I never found any use for them myself. I find the default route to be a good solution for 99% of the cases.
Eran Galperin
July 15, 2008 at 3:27 pm
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?
Nathan Wright
July 15, 2008 at 6:25 pm
@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 :)
Neil Garb
July 15, 2008 at 8:02 pm
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.
Seb Barre
July 15, 2008 at 8:10 pm
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.
phpimpact
July 15, 2008 at 8:18 pm
@Eran
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)
phpimpact
July 15, 2008 at 8:33 pm
Good stuff Federico, will bear this in mind if I ever start using lots of custom routes.
Dave Marshall
July 15, 2008 at 8:44 pm
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.
Jon
July 16, 2008 at 1:18 am
[...] Improving the Performance of Zend Controller [...]
PHP, Puppies, and other Geekery
July 16, 2008 at 1:52 am
@Jon: Yes, another advantage of doing it this way is that you can create self-contained modules:
blog/
|-- controllers/
|-- i18n/
| `-- en/
|-- models/
|-- properties/
| |-- BlogConfig.php
| |-- BlogRoutes.php
| `-- BlogDependencies.php
`-- views/
You can read more about this here.
phpimpact
July 16, 2008 at 9:04 am
Django does something similar to this. Each Django app can have its own urls.py. As said before it makes the modules more self-contained.
Eric J
July 16, 2008 at 7:48 pm
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.
phpimpact
July 20, 2008 at 1:58 pm
[...] Cargnelutti расказал как увеличить производительность Zend_Controller при помощи [...]
planet-php.ru
July 22, 2008 at 11:41 am
[...] Recent benchmarks show that the Zend Framework is slower than other Web frameworks. Another issue, explained here, is the design of the Router. If an application increases in complexity and attracts more traffic, [...]
Zend Framework Architecture
July 28, 2008 at 12:24 am
I am wondering if you have ever though about making this a proposal in the ZF wiki (http://framework.zend.com/wiki/display/ZFPROP/Home)? I think this would be a good feature too have available in a stock install of ZF.
Jon
July 30, 2008 at 11:42 pm
I posted a message on Ben’s blog. He’s the one updating the router.
Federico
July 30, 2008 at 11:56 pm
[...] this: Improving the performance of Zend_Controller PHP::Impact ( [str Blog] ) Maybe You are also using ajax which initiates framework on every [...]
Causing HUGE load on the Server - Zend Framework Forum
July 31, 2008 at 4:04 pm
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.
Brenton Alker
August 3, 2008 at 1:20 pm
Hi Brenton, here is a way of encapsulating routes into modules.
Federico
August 3, 2008 at 8:56 pm
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.
Koen
August 22, 2008 at 4:13 pm
Hmm, that’s a tricky one, considering that a module can have n amount of routes, and an application n amount of modules.
Federico
August 22, 2008 at 8:05 pm
[...] Improving the performance of Zend_Controller [...]
Zend Framework Controller: 22% Drop in Responsiveness « PHP::Impact ( [str Blog] )
September 16, 2008 at 11:04 pm
[...] Improving the performance of Zend_Controller Posted by Federico Filed in Frameworks, PHP [...]
Zend Framework: My_View_Helper_RouteUrl
October 4, 2008 at 12:08 pm
[...] to implement the default Front Controller? What if you don’t need all that flexibility, a URL mapper, a ViewRendered plugin or an ActionStack helper. Maybe all you need is just a couple of [...]
Implementing your own Front Controller in Zend Framework « fede.carg ( blog )
April 5, 2009 at 12:12 pm
[...] http://blog.fedecarg.com/2008/07/15/improving-the-performance-of-zend_controller/ [...]
Zend Framework: Optimizing Custom Routes « blog.concretegong.co.uk
June 2, 2009 at 2:27 pm