Routing is an essential part of every MVC, Magento is not an exception to this rule. In order to understand how Magento routing works, it is necessary to get acquainted with execution flow that leads to certain request getting (or not getting) dispatched. It goes without saying that this knowledge comes in handy when you need to explain that 404 being thrown.
Request processing starts as the last step after Mage::run()
triggered from index.php
triggers Mage_Core_Model_App::run()
to initialize Magento:
https://github.com/Marko-M/magento-ce/tree/1.9.0.1/app/code/core/Mage/Core/Model/App.php#L354
Important thing to point out is that request processing will not be triggered if we initiate Magento using Mage_Core_Model_App::init()
, something Magento cron.php
and shell scrips use, because this initialization mode is intended for use in situations where request processing isn't required (cron for example).
During a call to Mage_Core_Controller_Varien_Front::dispatch()
, Mage_Core_Model_App's getFrontController()
function triggers Mage_Core_Controller_Varien_Front's init()
function in order to initialze front controller, more precisely it iterates over routers to collect routes using their collectRoutes()
function triggered here:
Something not related to this topic, but very important, this is the place where custom routers added trough controller_front_init_routers
event or trough config/default/web/routers
XML path get added to Magento request processing system.
For built in Mage_Core_Controller_Varien_Standard
router, collecting routes means collecting all nodes defined in config.xml files using declarations like following:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <config> <frontend> <routers> <mmartinovic_productlist> <use>standard</use> <args> <module>Mmartinovic_Productlist</module> <frontName>productlist</frontName> </args> </mmartinovic_productlist> </routers> </frontend> </config> |
Mage_Core_Controller_Varien_Front::collectRoutes()
also detects controller rewrites created using "before" attribute of module nodes. Described process of collecting routes takes place here:
After front controller is initiated Mage_Core_Controller_Varien_Front::dispatch()
is triggered on initiated front controller instance:
This function contains crucial part of Magento request processing, one where each router's match()
function is being triggered inside foreach in and attempt to match the request. Here's the important code snippet from Mage_Core_Controller_Varien_Front::dispatch()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php // ... while (!$request->isDispatched() && $i++<100) { foreach ($this->_routers as $router) { if ($router->match($this->getRequest())) { break; } } } Varien_Profiler::stop('mage::dispatch::routers_match'); if ($i>100) { Mage::throwException('Front controller reached 100 router match iterations'); } // ... |
So from router's point of view, most important function in request processing is match()
and it's code is as follows:
Mage_Core_Controller_Varien_Router_Standard
uses information extracted from request object (instance of Mage_Core_Controller_Request_Http
) to do comparison of current request module name with collected routes front names. In case of a positive match, path to controller file for matched controller name is constructed and this path is validated. If matching controller file exists and contains valid controller class, it gets instantiated and checked for requested action (again, this is something extracted from the request object). If we have a positive match for action, request is marked as dispatched and dispatch()
function of matched controller is initiated to pass control forward to controller, what consequently leads to correct action method getting triggered.
If any of previous steps were negative, match()
function returns false and next router is being taken trough similar process (although this depends on the router's implementation of match()
function). If current request doesn't get matched by any router, Magento manually sets the controller on the request object to the Mage_Cms_IndexController
, and uses noRouteAction
action what renders CMS page configured in System -> Configuration -> General -> Web -> Default Pages -> CMS No Route Page (by default CMS page with no-route code). If this CMS page has been deleted, the defaultNoRouteAction
action is dispatched.
Back to our routers, Magento comes with four built-in routers, and they are checked for matching routes in the following order:
- Mage_Core_Controller_Varien_Router_Admin
- Mage_Core_Controller_Varien_Router_Standard
- Mage_Cms_Controller_Router
- Mage_Core_Controller_Varien_Router_Default
That's basically what it's all about. You will probably agree that in spite of complexity it brings, Magento has very robust and extensible routing system?