The centerpiece of Magento, the Front Controller, can sometimes be a challenge for a beginner Magento developer. Just a few years ago most PHP applications where collections of scripts, which were supposed to be accessed by their own URL to perform their function. The MVC and Front Controller patterns change this approach by letting one object to handle all requests. Front Controller instantiates objects, fires events and sets up the application behavior according to the received request. This is a significant paradigm shift that can be a source of much confusion. The purpose of this article is to explain how request dispatching works, how router configuration is compiled and read, and how router settings can be configured.
A quick look into Magento .htaccess file reveals that most requests are routed to the index.php, the one in the root folder of your application. Exceptions are requests for which a matching resource (folder or a file) can be found. To process a typical request Magento calls a Front Controller, an instance of Mage_Core_Controller_Varien_Front class. While being initialized the Front Controller reads router configuration collected from all the config.xml files and builds its router collection. By the point the request is passed to it for dispatching the Front Controller has all the information required to find a matching controller.
You can define routers for your modules in their respective config.xml files. This is also the way the core modules define their routers. Routers can be defined for front end as well as for back end functions. Looking through the config.xml files available for your default installation of Magento we can derive a general structure of a router configuration.
Fig.1 Structure of a router configuration entry (back end router)
<config> ---- <admin><!-- 1 --> <routers><!-- 2 --> <modulename><!-- 3 --> <use>admin</use><!-- 4 --> <args><!-- 5 --> <frontName>frontname</frontName><!-- 6 --> <module>Packagename_Modulename</module><!-- 7 --> <modules><!-- 8 --> </modules> </args> </modulename> </routers> </admin> --- </config>
1 and 2: These are the parent nodes. 1 can be either <admin> (for admin routers) or <frontend> (for frontend routers)
3: The name of your module in lowercase. You can indicate a name that is already in use (for example adminthml) only if you want to extend the configuration of that route with a child entry in <modules> node (see below). Other settings will not be rewritten or may even cause errors.
4: The <use> switch tells the system if this is an back end or a front end router. Correspondingly, the possible values are “admin” or “standard”.
5: The <args> node can contain three different types of arguments; see below (6,7,8).
6: <frontName> sets the router name as it will be included in the URL: http://www.yoursite.com/frontname/controller/action.
7: The node <module> contains the full name of your module including package (Packagename_Modulename). The system will then look for controllers in the controllers directory of the module you indicated.
8: This node contains a list of modules for the system to consider when looking for controllers. It can be used to extend the router configuration from other modules. More details on that in the next section.
Extending router configuration with the <modules> node
The <modules> node is used to extend a previously defined router by telling the system where else to look for controllers. This settings is mostly set in config files of modules other than the one where the router was originally defined. For example, you want to make your back end module available under “adminhtml” router. You want to tell the system that your module also contains controllers for this router. You don’t need to change the config file of the “Adminhtml” module, you extend configuration of the adminthml router instead. In the config file of your module add <adminhtml> node to you router configuration and provide children nodes for the <modules> node:
Fig 2. Extending a router configuration
<admin> <routers> <adminhtml> <args> <modules> <Packagename_Modulename_Adminhtml before="Mage_Adminhtml">Packagename_Modulename_Adminhtml</Packagename_Modulename_Adminhtml> </modules> </args> </adminhtml> </routers> </admin>
As you can see, the children of the <modules> node have the following structure:
Your child node is the name of your module including the package name. It can also contain the name of a directory where you put controllers, which extend the original router’s configuration. It makes sense to separate the controllers you defined for your modules and the ones that will be available under another router. In the example above the sub directory name is Adminhtml. The controllers intended to be used under the “adminhtml” router will be placed in the directory Adminhtml under controllers folder:
Packagename -> Modulename -> controllers -> Adminhtml
The children of the <modules> node can also contain “position” attributes. They can be “before” or “after”. When reading the router’s configuration the system builds a list of all the modules that contain controllers for the router. While routing the request the system searches the contollers folders of these modules for a passing controller. The order in which the modules are inspected is defined by these attributes. If you want to make sure that your module will be searched before the original one, then set the “before” attribute values to “Originalpackage_Originalmodule” (in our example that is “Mage_Adminhtml”).
Fig 3. Real life example of an extended router configuration
<admin> <routers> <adminhtml> <args> <modules> <Mage_Paypal before="Mage_Adminhtml">Mage_Paypal_Adminhtml</Mage_Paypal> </modules> </args> </adminhtml> </routers> </admin>
Above is an excerpt from the configuration file of the module “Mage_Paypal”. Here the backend controllers of the module are made available under the router defined for the “Mage_Adminthml” module. Before looking for a Paypal backend controller, the system will first inspect the controllers folder of the “Mage_Paypal” module, as defined in the “before” attribute”.
Magento routing algorithm
So far you have familiarized yourself with the way routers are configured and extended. Now we can look at the whole picture by going through the algorithm implemented by Magento for request dispatching.
It all starts with a request being passed to the Front Controller. The request is now in a form of a PHP object and one of its features is the request URL. The URL can already be in the standard form /frontname/controller/action/… or it can be in the user/SEO-friendly form. For the latter the Front Controller has to call rewrite functions that extract a real path (again /frontname/controller/action/…) from the database based on the rewrite rules generated by the system or set up manually by the shop admin.
The next stage is to check all four router groups for the frontname. Router groups are routers banded together by their application area, they can be “admin” for back end functions, “standard” for front end functions, or “cms” and “default”. The routers you define in your modules are assigned to either “admin” or “standard” groups. The “cms” router group receives requests for cms pages, such as “About us”. The “default” router serves certain system functions, for example it is used to display the 404 “not found” page. The system checks if a request has already been dispatched and if not extracts the router frontname from the URL path. The frontname is then searched for in all the modules of the router group. If such module is not found, the system proceeds to the next group.
If a frontname match was found the system creates a list of modules in which to look for the controller. For most routers the list will have only one module defined in the <module> node. If other modules extend the router with their <modules> entries, they will be included into the list. The order in which modules are placed into the list is defined by the position attributes I mentioned above.
The system extracts the controller name from the URL and goes through the module list checking if a passing controller class file exists in each module’s controllers folder. Once such file is found the system attempts to instantiate a class and check if it is a valid controller class and if a matching action is available. If everything matches up, the request is marked as dispatched and is passed to the controller for further processing.
If no match is found in all router roots and all modules, the system throws an exception “Front controller reached 100 router match iterations”. It usually does not come to that, as unknown URLs are processed by the default router, which calls the “noRoute” controller of the “cms” module and its action “noRoute”. This way a properly styled 404 error page is shown.
To sum up
You have learned how the request dispatching works in Magento and looked into the router configuration possibilities. This information should help you configure your routers not only by following examples and tutorials but also by understanding the internal mechanics of Magento routing.