Categories


Archives


Recent Posts


Categories


In Depth Magento Dispatch: Stock Routers

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

We’re in the middle of a series covering the various Magento sub-systems responsible for routing a URL to a particular code entry point. So far we’ve covered the general front controller architecture and the Standard Magento router object. Today we’re covering the three remaining stock router objects. You may want to bone up on parts one and two before continuing.

Notice: While the specifics of this article refer to the 1.6 branch of Magento Community Edition, the general concepts apply to all versions.

The Admin Router Object

Today we’ll start with the admin router object, defined by the Mage_Core_Controller_Varien_Router_Admin class

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
class Mage_Core_Controller_Varien_Router_Admin extends Mage_Core_Controller_Varien_Router_Standard
{
    ...
}

Of the four routers that ship with Magento, the admin router object is the first router object instantiated and checked for a match. This is the router object responsible for dispatching requests to the admin console application, which is sometimes known as the adminhtml, (not admin) Magento area. Naming gets a little tricky this deep into the system, as many of the concepts in the initial Magento system evolved into the system we have today.

Let’s get started.

Matchmaker Matchmaker, Make m– hello?

If you open the Admin.php file mentioned above, the first thing you’ll notice is there’s no match method defined. After a little head scratching, you’ll realize the obvious: The match method must be defined on the Admin router’s parent class. We’ll just hop up a level to find the match method

#File: app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
class Mage_Core_Controller_Varien_Router_Standard extends Mage_Core_Controller_Varien_Router_Abstract
{
    ...
    public function match(Zend_Controller_Request_Http $request)
    {
        ...
    }
    ...
}

Take a closer look at the parent classname. It’s Mage_Core_Controller_Varien_Router_Standard, which is also the class of our second router, and as we’ve learned previously contains the routing logic for the frontend application.

In some ways this is good. It means the admin console application and the cart application share the same routing logic. However, the way things are architected lends itself to some confusion, and leads to a lot of special case statements for the Admin router inside the Standard router’s match method.

In retrospect, a more “Magento like” approach would have been a base Standard router class, (probably Abstract), that contained shared routing logic, and then a Cart router for the frontend cart application, and an Admin router for the admin console application. We mention this mainly as a reminder that the metaphors on this level are a little muddled, and the logical conclusions your programmer trained brain will jump to might be the wrong logical conclusion.

Rather than run through the entire routing match logic again, we’re going to highlight the differences in the Admin routing, as well as the rational for some of the special cases.

It’s the Little Differences

First up is the _beforeModuleMatch method. You’ll recall this method is one of the first things called in the match method. In the Admin router, this always returns true.

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
protected function _beforeModuleMatch()
{
    return true;
}

However, if we pop down to the Standard router, we see

#File: app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
protected function _beforeModuleMatch()
{
    if (Mage::app()->getStore()->isAdmin()) {
        return false;
    }
    return true;
}

So it looks like this method is here to ensure the Standard router object bails if, for some reason, the store model object thinks its in admin mode. Like the Standard/Admin router relationship, the store
object is another one of those things that points to certain parts of the Magento development process being focused on the frontend application first, and then tacking on an admin console later and having to back-port changes.

The store object is a model that really only applies to the frontend/cart application. However, because so much code in Magento assumes the store object exists, it needs to be available to the admin console application. This, in turn, creates trouble at the router level, which is what leads to a check like this. Many layers of abstraction, no defined contracts between classes/modules, and the fear of refactoring created by the lack of tests will always lead to these sorts of situations.

The next difference to consider is the related _afterModuleMatch method. As a reminder, here’s how it’s used in the match method

#File: app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
//checkings after we foundout that this router should be used for current module    
if (!$this->_afterModuleMatch()) {
    return false;
}

In the admin router class, this _afterModuleMatch method is defined as

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
/**
 * checking if we installed or not and doing redirect
 *
 * @return bool
 */
protected function _afterModuleMatch()
{

    if (!Mage::isInstalled()) {
        Mage::app()->getFrontController()->getResponse()
            ->setRedirect(Mage::getUrl('install'))
            ->sendResponse();
        exit;
    }
    return true;
}

This code will redirect a user to the installation wizard the first time Magento is run. While it doesn’t have anything to do with the admin console routing, it’s here because the admin routing object is the first router object that’s checked. In hindsight, this logic probably belongs somewhere higher up the stack, perhaps in the front controller object. It’s also arguable there should be a Installer routing object that’s checked first. The third argument, and the one that always wins, is to leave the code where it is, because it works. You know the drill.

Moving on, the admin router object also defines its own fetchDefault method.

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
public function fetchDefault()
{
    // set defaults
    $d = explode('/', $this->_getDefaultPath());
    $this->getFront()->setDefault(array(
        'module'     => !empty($d[0]) ? $d[0] : '',
        'controller' => !empty($d[1]) ? $d[1] : 'index',
        'action'     => !empty($d[2]) ? $d[2] : 'index'
    ));
}

You’ll recall that this is the method that sets a default module name, controller name, and action name on the front controller, which the match method will ask for if it can’t derive a name. As you can see, the Admin router gets these defaults by calling _getDefaultPath, which pulls the path directly from the merged Magento config.xml tree

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
protected function _getDefaultPath()
{
    return (string)Mage::getConfig()->getNode('default/web/default/admin');
}

You’ll recall the _getDefaultPath method is used by the standard router, but not to set these defaults. Instead it’s used to provide path information for the root level URL. Again, we’re seeing a situation that seems like different camps with different visions coming into conflict with one another leading to muddled metaphors.

Similarly, the _shouldBeSecure method also needs some custom admin code to apply a slightly different set of logic based on values fetched from the admin config settings

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
protected function _shouldBeSecure($path)
{
    return substr((string)Mage::getConfig()->getNode('default/web/unsecure/base_url'),0,5)==='https'
        || Mage::getStoreConfigFlag('web/secure/use_in_adminhtml', Mage_Core_Model_App::ADMIN_STORE_ID)
        && substr((string)Mage::getConfig()->getNode('default/web/secure/base_url'),0,5)==='https';
}

and the same goes for _getCurrentSecureUrl

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
protected function _getCurrentSecureUrl($request)
{
    return Mage::app()->getStore(Mage_Core_Model_App::ADMIN_STORE_ID)
        ->getBaseUrl('link', true) . ltrim($request->getPathInfo(), '/');
}

Still more examples where the needs of the application continue to permeate into the base framework/system code.

Raiders of the Lost 404

Next up is the _noRouteShouldBeApplied method (as defined in the Admin router)

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
protected function _noRouteShouldBeApplied()
{
    return true;
}

In the Standard router class, _noRouteShouldBeApplied was hard coded to return false. This method comes into play late in the match logic. If we have a valid module, but are unable to find a controller and action match, we called _noRouteShouldBeApplied as follows

#File: app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
if (!$found) {
    if ($this->_noRouteShouldBeApplied()) {
        $controller = 'index';
        $action = 'noroute';
        $controllerClassName = $this->_validateControllerClassName($realModule, $controller);
        if (!$controllerClassName) {
            return false;
        }

        // instantiate controller class

        $controllerInstance = 
        Mage::getControllerInstance($controllerClassName, $request, $front->getResponse());

        if (!$controllerInstance->hasAction($action)) {
            return false;
        }
    } else {
        return false;
    }
}

In the Standard router, this branch is always skipped. However, in the Admin router things get interesting. This appears to be an older code path that, if no controller or action was found for a module, would make one last ditch effect to look at the Magento module’s index controller (if it exists), and call a noRoute action method. My guess is this was meant to provide the admin console with a 404 page for its own for urls, such as

http://magento.example.com/admin/notthere

However, this appears to have been broken at some point. If you look at the call to create and validate a controller class name

$this->_validateControllerClassName($realModule, $controller);

you can see its using the last $realModule that was found in the main foreach loop. At some point in the release history, Magento added the Phoenix_Moneybookers module to its default distribution. This module configures a second $realModule for the router to check. Because this module has no index controller, that means this particular branch will always return false, because $realModule will always be Phoenix_Moneybookers at this point in the code.

It’s easy to see how fixing a bug like this gets de-prioritized over building actual features, but it’s equally easy to see how this additional, now obsolete, code branch makes the entire routing process that much more opaque.

For what it’s worth, you can see the noroute action in the base Admin controller here

#File: app/code/core/Mage/Adminhtml/Controller/Action.php
public function norouteAction($coreRoute = null)
{
    $this->getResponse()->setHeader('HTTP/1.1','404 Not Found');
    $this->getResponse()->setHeader('Status','404 File not found');
    $this->loadLayout(array('default', 'adminhtml_noroute'));
    $this->renderLayout();
}    

This, in turn, triggers the following layout update XML, setting a 404 message

<!-- File: adminhtml/default/default/layout/main.xml -->
<adminhtml_noroute>
    <reference name="content">
        <block type="core/text" name="content.noRoute">
            <action method="setText" translate="text" module="adminhtml">
                <text>
                <![CDATA[
                <h1 class="page-heading">404 Error</h1>
                <p>Page not found.</p>
                ]]>
                </text>
            </action>
        </block>
    </reference>
</adminhtml_noroute>

Route Collecting

Finally, we have what many would consider the main difference in the Admin router, collecting routes.

First off, when collectRoutes($configArea, $useRouterName) is called during router instantiation, both the $configArea and $useRouterName are set to the string admin. This means our collectRoutes method will be looking for routers at

admin/routers

instead of

frontend/routers

and will be looking for a node with the value

<use>admin</use>

instead of

<use>standard</use>

In addition to this, collectRoutes is redefined to do some jiggery pokery before calling back to the parent collectRoutes

#File: app/code/core/Mage/Core/Controller/Varien/Router/Admin.php   
public function collectRoutes($configArea, $useRouterName)
{
    if ((string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_USE_CUSTOM_ADMIN_URL)) {
        $customUrl = (string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_CUSTOM_ADMIN_URL);
        $xmlPath = Mage_Adminhtml_Helper_Data::XML_PATH_ADMINHTML_ROUTER_FRONTNAME;
        if ((string)Mage::getConfig()->getNode($xmlPath) != $customUrl) {
            Mage::getConfig()->setNode($xmlPath, $customUrl, true);
        }
    }

    parent::collectRoutes($configArea, $useRouterName);
}

This additional if block implements the custom admin url feature, configured at

System -> Configuration -> Admin -> Custom Admin Path

This is interesting for a few reasons. The first is, the developer who worked on this feature didn’t use the provided API for accessing store configuration variables, Mage::getStoreConfig. Instead they dove directly into the configuration tree. The results are the same, but it either points to a difference of opinion about the best way to access config variables or that using getStoreConfig, which triggers instantiation of a store, caused yet another conflict with something because we’re not on the frontend application.

The second reason this is interesting is how the feature was implemented. Rather than monkey with the request to achieve a new admin URL, the developer dives into the already merged configuration tree and changes the value at

admin/routers/adminhtml/args/frontName

This alters the adminhtml router’s configuration node at runtime such that this

<!-- File: app/code/core/Mage/Core/Adminhtml/etc/config.xml -->
<adminhtml>
    <use>admin</use>
    <args>
        <module>Mage_Adminhtml</module>
        <frontName>admin</frontName>
    </args>
</adminhtml>

will look like this

<!-- never in a file, just in memory or cache -->
<adminhtml>
    <use>admin</use>
    <args>
        <module>Mage_Adminhtml</module>
        <frontName>secret_admin</frontName>
    </args>
</adminhtml>

That’s extremely unorthodox, but also very clever. I imagine whomever came up with it has some epic battles with the guys who said adding a custom admin URL would be too much work and introduced too much risk to the existing codebase.

Admin Wrap Up

Other than the above exceptions, the Admin routing code behaves identical to the Standard routing code. The configuration is searched for potential modules, then the potential modules are searched for a matching controller and action, and if found, the action is dispatched.

That said, although it’s possible to define multiple front names for the admin router, I’d advise against doing so (despite previous advice). There are parts of the admin console application that assume a front name of admin. While pages will load and work with alternate admin frontnames, certain features (such as the media gallery integration for the rich text editor), may not.

While it’s possible to work around these special cases, you’re better off slipping your module into the admin front name/adminhtml config router node, and then being careful there’s no overlap between your controller names and the Mage_Adminhtml module controller names.

The CMS Router Object

We’ve reached our third router object, the Cms router, defined by the class Mage_Cms_Controller_Router.

The Cms router is a little different from the others. First off, it’s added to the system via a listener for the controller_front_init_routers event. Additionally, it doesn’t inherit from the Standard router class, and its purpose isn’t to determine which action controller to use. Instead, the Cms router’s purpose is to determine if the request URL matches a content page in the database, and if so ensure a page is rendered with that content. Earlier we said that the Cms router kicked off an MVC system. That was a bit of a fib to smooth over some of the inherent complexity of Magento’s routing system.

If that didn’t make sense don’t worry, all will become clear. Let’s get right to it.

Entering the Cms router’s match method, the first thing we see is a quick, paranoid sanity check to ensure first-run users are sent to the installer.

#File: app/code/core/Mage/Cms/Controller/Router.php
public function match(Zend_Controller_Request_Http $request)
{
    if (!Mage::isInstalled()) {
        Mage::app()->getFrontController()->getResponse()
            ->setRedirect(Mage::getUrl('install'))
            ->sendResponse();
        exit;
    }
    ...
}

It’s not certain if this is just healthy paranoia, or if there were certain cases where other redirects to the installer failed and one got through to the Cms router. Sound familiar?

Irrespective of all that, we can safely ignore this block of code and move on to the next.

#File: app/code/core/Mage/Cms/Controller/Router.php
$identifier = trim($request->getPathInfo(), '/');

Although the syntax is slightly different, we’re still on track with the other router objects here. We’re grabbing the normalized path information of the URL. A URL like this

http://magento.example.com/about-magento-demo-store

will result in an $identifier containing the string about-magento-demo-store. Next up though

#File: app/code/core/Mage/Cms/Controller/Router.php
$condition = new Varien_Object(array(
    'identifier' => $identifier,
    'continue'   => true
));

Mage::dispatchEvent('cms_controller_router_match_before', array(
    'router'    => $this,
    'condition' => $condition
));

is something a little different. Before trying to match, Magento fires off an event. The $condition object is a transport object, used to send information off to the event listeners.

#File: app/code/core/Mage/Cms/Controller/Router.php
$identifier = $condition->getIdentifier();

if ($condition->getRedirectUrl()) {
    Mage::app()->getFrontController()->getResponse()
        ->setRedirect($condition->getRedirectUrl())
        ->sendResponse();

    $request->setDispatched(true);
    return true;
}

if (!$condition->getContinue()) {
    return false;
}

After the event fires, we examine the object in $condition. First, we pull out the identifier. Secondly, we check the object for a redirect_url property (magic getters/setters), and if it exists we use it to fire off an HTTP redirect. Finally, if the condition property has been set to false, the Cms router stops trying to match, and passes control on to the next router object by returning false.

There’s a few things here that could use some clarification. First, by looking at this code, we can infer what the intention of the cms_controller_router_match_before event is. It allows users to intercept a CMS page request and based on custom programatic rules in the observer

  1. Change the page that’s going to be rendered
  2. OR trigger an HTTP redirect
  3. OR halt the request

Secondly, the redirection code

#File: app/code/core/Mage/Cms/Controller/Router.php
Mage::app()->getFrontController()->getResponse()
    ->setRedirect($condition->getRedirectUrl())
    ->sendResponse();

$request->setDispatched(true);
return true;

can be a little confusing. If the redirect_url property is an external http url, Magento will redirect with a Location header and while the browser goes off to another page, the rest of the system code executes in the background. The response object’s sendResponse doesn’t automatically exit for a web server based redirection.

Next up, if we’re still here, we use the identifier to load a CMS model from the system.

#File: app/code/core/Mage/Cms/Controller/Router.php
$page   = Mage::getModel('cms/page');
$pageId = $page->checkIdentifier($identifier, Mage::app()->getStore()->getId());

It might be more appropriate to say we fetch the database id of the CMS page that matches the identifier, since we don’t actually load the model. You can checkout the resource model method that ultimately gets called if you’re interested in the details

#File: app/code/core/Mage/Cms/Model/Resource/Page.php
public function checkIdentifier($identifier, $storeId)
{
    $stores = array(Mage_Core_Model_App::ADMIN_STORE_ID, $storeId);
    $select = $this->_getLoadByIdentifierSelect($identifier, $stores, 1);
    $select->reset(Zend_Db_Select::COLUMNS)
        ->columns('cp.page_id')
        ->order('cps.store_id DESC')
        ->limit(1);
    return $this->_getReadAdapter()->fetchOne($select);
}

As you can see, it’s a simple select to the CMS database table.

Dispatching a CMS Page

Up next is this bit of code

#File: app/code/core/Mage/Cms/Controller/Router.php
if (!$pageId) {
    return false;
}

$request->setModuleName('cms')
    ->setControllerName('page')
    ->setActionName('view')
    ->setParam('page_id', $pageId);

$request->setAlias(
    Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS,
    $identifier
);

return true;

If there was no matching $pageId, we return false and allow the next router in the system to have its chance. However, if we did find a match, we set a module name, controller name and action name, as well as a request parameter for the page ID, and then return true.

All of which seems a little premature. We’ve indicated there’s a match by returning true, but we haven’t done anything to produce page output. What gives?

If you’re stumped don’t be ashamed, because it’s a little tricky and counter intuitive. While we did return true, nothing set the controller as dispatched. So, back up in our front controller’s foreach loop

File: app/code/core/Mage/Core/Controller/Varien/Front.php
while (!$request->isDispatched() && $i++<100) {            
    foreach ($this->_routers as $router) {
        if ($router->match($this->getRequest())) {
            break;
        }
    }
}

we bail/break on the foreach loop because of the match, but because the Cms router never marked the request as dispatched, we stay in the while loop and start trying to match on ALL the routers again. This means after the first CMS match is called, we call match on the Admin router, and then the Standard router.

You may be thinking to yourself

If they didn’t match before, won’t we just reach the Cms router again?

It’s a little confounding until you remember back to the epic standard router match method, and calls like this

#File: app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
if ($request->getModuleName()) {
    $module = $request->getModuleName();

...

if ($request->getControllerName()) {
    $controller = $request->getControllerName();

...



if ($request->getActionName()) {
    $action = $request->getActionName();

At the time they seemed like useless noise, but now we have enough information to realize why they’re there. The Cms router’s match method altered the request object, and set these very properties, which will allow the Standard router to match the second time through. That means a URL like this

http://magento.example.com/about-magento-demo-store

will actually be dispatched by the Standard router as though it was called with a URL whose path information was

cms/page/view/page_id/3

which means that all non-home CMS page requests are handled by the viewAction method of Mage_Cms_PageController

#File: app/code/core/Mage/Cms/controllers/PageController.php
class Mage_Cms_PageController extends Mage_Core_Controller_Front_Action
{
    public function viewAction()
    {
        $pageId = $this->getRequest()
            ->getParam('page_id', $this->getRequest()->getParam('id', false));

        if (!Mage::helper('cms/page')->renderPage($this, $pageId)) {
            $this->_forward('noRoute');
        }
    }
}    

This action method passes the page id onto the cms/page helper, which ultimately renders the page. (If you’re interested in the actual rendering process, No Frills Magento Layout covers that, and much more)

CMS Wrap Up

After looking at the Cms router object, it becomes clear that our original assessment of what a Router object should do

  1. Provide a match method which examines the request object and returns true if the router wishes to “claim”
    a request and stop other router objects from acting

  2. Mark the request object as dispatched, or through inaction fail to mark it as dispatched

  3. Set the body/contents of the request object, either directly or via another Magento system

is flawed. The CMS router does some, but not all, of this. Instead of kicking off a system to produce output, it jiggers the request so it can be interpreted again by the Standard router. Of course, if that’s the real correct usage, why wasn’t the Admin router built the same way? We ask not because there’s a right answer, but to stress that these sorts of questions are the ultimate undoing of many configuration based, deeply abstract, systems. Without a clear, firm hand guiding development and guiding usage, systems become a tangled mess of code that works, but lacks any central vision and becomes difficult for outsiders to follow.

The Default Router Object

This brings us to our final object, the Default router, defined by the class Mage_Core_Controller_Varien_Router_Default. The Default router is added last, and is there to signal the rest of the system that the URL/request has no MVC, CMS, or third party route match.

Compared to the others, the match method here is relatively straight forward. First, we derive a default module, controller, and action name from the web/default/no_route config node

#File: app/code/core/Mage/Core/Controller/Varien/Router/Default.php
public function match(Zend_Controller_Request_Http $request)
{
    $noRoute        = explode('/', Mage::app()->getStore()->getConfig('web/default/no_route'));
    $moduleName     = isset($noRoute[0]) ? $noRoute[0] : 'core';
    $controllerName = isset($noRoute[1]) ? $noRoute[1] : 'index';
    $actionName     = isset($noRoute[2]) ? $noRoute[2] : 'index';
    ..
}

If there’s no value set for the config node, or the value set is only a partial path, the hard coded defaults of core, index, and index are used.

The configuration node web/default/no_route corresponds to the system configuration pane at

Admin -> System -> Configuration -> Web -> Default Pages -> Default No-route URL

In a stock system, this value is set to

cms/index/noRoute

meaning most Magento 404 pages will be routed to the noRouteAction method of Mage_Cms_IndexController.

Back to the match method, next we have another special case for the admin console

#File: app/code/core/Mage/Core/Controller/Varien/Router/Default.php
if (Mage::app()->getStore()->isAdmin()) {
    $adminFrontName = (string)Mage::getConfig()->getNode('admin/routers/adminhtml/args/frontName');
    if ($adminFrontName != $moduleName) {
        $moduleName     = 'core';
        $controllerName = 'index';
        $actionName     = 'noRoute';
        Mage::app()->setCurrentStore(Mage::app()->getDefaultStoreView());
    }
}

The exact edge case this code handles is unclear. It looks as though it’s there to ensure if the store is set to Admin mode and our derived module name doesn’t match the current front name configured for the admin module, that we unset admin mode and pass the user to the Mage_Core module’s index controller’s noRoute action. We’ve already talked a bit about the weirdness of the admin 404 process, this would appear to be another example of that. (If anyone has information about the intent of this code, please get in touch)

Finally, similar to the Cms router, we set these values on the request object and return true without marking the request as dispatched.

#File: app/code/core/Mage/Core/Controller/Varien/Router/Default.php
$request->setModuleName($moduleName)
    ->setControllerName($controllerName)
    ->setActionName($actionName);

return true;

Just as with the Cms router, this will signal the front controller to send the request through the router objects again, allowing the existing Magento systems to handle a noRoute situation. If you’re interested in the various ways this can happen, it’s worth reading the Magento’s Many 404 Pages article.

Router Wrap Up

That, in a nutshell, is Magento’s routing system. As you’ve can see, there’s a lot of lingering code and patterns still kicking around for legacy reasons, and this legacy behavior can often make it difficult to suss out the true intentions of the system. Based on news from the MDP conference this year, and small steps taken in the final 1.6 CE release, it appears the core team is slowly cleaning up these legacy system in preparation for the Magento 2 refactoring release. Hope springs eternal.

For people starting out with Magento, I’d say best practices are to stay out of this system. Learn how to setup CMS pages and MVC controllers for the front and backend, and then just treat the code as a black box. A key to working with every software system is to trust that the sub-systems will do their job, or at least accept that they’re going to do what they’re going to do, and focus on writing code to achieve your own goals. When you’re ready to dive a little deeper, I hope these guides can help you explore the cavernous depths.

Next time we’ll be wrapping up the series by exploring the system with one of the higher usage/misunderstanding ratio, the rewrite system. While not technically part of the routing system, Magento’s two request rewrite systems impact the request object that the router objects deal with, and therefore no discussion of routing is complete without diving in.

If you’ve found any of this useful, you’ll find Pulse Storm’s Magento products equally useful and time saving. If your retail senses don’t need stimulating at the moment, please consider donating a few bucks to the continued development of these tutorials.

Originally published September 12, 2011
Series Navigation<< In Depth Magento Dispatch: Standard RouterIn Depth Magento Dispatch: Rewrites >>