Custom router in Magento

router hab with ethernet cables computer network

Creating the custom router could be very useful thing when we want to separate some business logic that can be applied on same route (url) without redirecting to different routes inside the controller.

Why don’t we create separate route for different logic and we are done?

Let me explain a little: I integrated a OneStepCheckout extension inside our custom project and that extension rewrites the Core Checkout routes already.
I needed to rewrite the routes also and even separate them on two different controllers depending on parameter inside URL. Because of that, I found that that time best way was to create a new router which will redirect to different controller depends on parameters.

Let’s define our router inside: “…/app/code/local/Mynamespace/Myextension/etc/config.xml”:

 <default>
    <web>
      <routers>
         <myextension_myrouter>
             <area>frontend</area>
             <class>Mynamespace_Myextension_Controller_Router</class>
         </myextension_myrouter>
     </routers>
    </web>
   </default>
 </stores>

Inside “…/app/code/local/Mynamespace/Myextension” create new folder named: Controller and create the PHP class: Mynamespace_Myextension_Controller_Router.php:

<?php
//Mynamespace_Myextension_Controller_Router.php
 
/**
 * @author Darko Goleš <darko.goles@inchoo.net>
 * Router for deciding to go on party checkout or regular onestepcheckout
 */
class Mynemaspace_Myextension_Controller_Router extends Mage_Core_Controller_Varien_Router_Standard {
 
 
}
?>

To avoid re-inventing the wheel, we could just copy and paste existing match method source code from Mage_Core_Controller_Varien_Router_Standard.php inside our class and make modifications to it:

  /**
     * Match the request
     *
     * @param Zend_Controller_Request_Http $request
     * @return boolean
     */
    public function match(Zend_Controller_Request_Http $request)
    {
        //checking before even try to find out that current module
        //should use this router
        if (!$this->_beforeModuleMatch()) {
            return false;
        }
 
        $this->fetchDefault();
 
        $front = $this->getFront();
        $path = trim($request->getPathInfo(), '/');
 
        if ($path) {
            $p = explode('/', $path);
        } else {
            $p = explode('/', $this->_getDefaultPath());
        }
 
        // get module name
        if ($request->getModuleName()) {
            $module = $request->getModuleName();
        } else {
            if (!empty($p[0])) {
                $module = $p[0];
            } else {
                $module = $this->getFront()->getDefault('module');
                $request->setAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS, '');
            }
        }
        if (!$module) {
            if (Mage::app()->getStore()->isAdmin()) {
                $module = 'admin';
            } else {
                return false;
            }
        }
 
        /**
         * Searching router args by module name from route using it as key
         */
        $modules = $this->getModuleByFrontName($module);
 
        if ($modules === false) {
            return false;
        }
 
        //checkings after we foundout that this router should be used for current module
        if (!$this->_afterModuleMatch()) {
            return false;
        }
 
        /**
         * Going through modules to find appropriate controller
         */
        $found = false;
        foreach ($modules as $realModule) {
            $request->setRouteName($this->getRouteByFrontName($module));
 
            // get controller name
            if ($request->getControllerName()) {
                $controller = $request->getControllerName();
            } else {
                if (!empty($p[1])) {
                    $controller = $p[1];
                } else {
                    $controller = $front->getDefault('controller');
                    $request->setAlias(
                        Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS,
                        ltrim($request->getOriginalPathInfo(), '/')
                    );
                }
            }
 
            // get action name
            if (empty($action)) {
                if ($request->getActionName()) {
                    $action = $request->getActionName();
                } else {
                    $action = !empty($p[2]) ? $p[2] : $front->getDefault('action');
                }
            }
 
            //checking if this place should be secure
            $this->_checkShouldBeSecure($request, '/'.$module.'/'.$controller.'/'.$action);
 
            $controllerClassName = $this->_validateControllerClassName($realModule, $controller);
            if (!$controllerClassName) {
                continue;
            }
 
            // instantiate controller class
            $controllerInstance = Mage::getControllerInstance($controllerClassName, $request, $front->getResponse());
 
            if (!$controllerInstance->hasAction($action)) {
                continue;
            }
 
            $found = true;
            break;
        }
 
        /**
         * if we did not found any siutibul
         */
        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;
            }
        }
 
        // set values only after all the checks are done
        $request->setModuleName($module);
        $request->setControllerName($controller);
        $request->setActionName($action);
        $request->setControllerModule($realModule);
 
        // set parameters from pathinfo
        for ($i = 3, $l = sizeof($p); $i < $l; $i += 2) {
            $request->setParam($p[$i], isset($p[$i+1]) ? urldecode($p[$i+1]) : '');
        }
 
        // dispatch action
        $request->setDispatched(true);
        $controllerInstance->dispatch($action);
 
        return true;
    }

For example, now I want to redirect route to different controller depending if some value “Party” is defined we could add prefix to controller name so regular controller will be:
Mynamespace/Myextension/controllers/Somecontrollername.php
and “Party” controller will be inside: Mynamespace/Myextension/controllers/Party/Somecontrollername.php

To implement this, let’s add some code between 57th and 62nd line of above source code:

//...
        $party_prefix = '';
 
        if ($this->isPartyOrder($request)) {
            foreach ($modules as $key => $realModule) {
                $modules[$key].='_Party';
            }
 
            $party_prefix = '_party';
        }
//...

Of course our isParty method looks like this:

..
    /**
     * Determine if order is "Party order"
     * @param Mage_Core_Controller_Request_Http $request
     * @return boolean
     */
    private function isPartyOrder($request) {
 
        if (isset($_SESSION['is_party_checkout']) && $_SESSION['is_party_checkout']) {
            return true;
        }
 
        $pathInfo = $request->getPathInfo();
 
        if (stristr($pathInfo, 'onestepcheckout/index/index')) {
            $params = Mage::app()->getRequest()->getParams();
            if (isset($params['id'])) {
                return true;
            }
        }
 
        return false;
    }
..

Here we check if it is Party order by session or by GET parameters inside URL.

Next let’s modify a route name that will be used in layout.xml files for our different routes and in place of this code:

  /**
         * Going through modules to find appropriate controller
         */
        $found = false;
        foreach ($modules as $realModule) {
            $request->setRouteName($this->getRouteByFrontName($module));

… we are going to change one line to look like this:

//...
        /**
         * Going through modules to find appropriate controller
         */
        $found = false;
        foreach ($modules as $realModule) {
            $request->setRouteName('ourextension_' . $this->getRouteByFrontName($module) . $party_prefix);
        ...
//...

Now we can define our layout handles in layout xml files separately for “Party” route and for regular:

<?xml version="1.0"?>
<layout version="0.1.0">
    <ourmodule_controllername_actionname>
      <block ....... some layout updates ... etc
    </ourmodule_controllername_actionname>
 
   <ourmodule_party_controllername_actionname>
      <block ....... some different layout updates ... etc
   </ourmodule_party_controllername_actionname>
</layout>

This way we can implement totally different layout for this two cases separately.

This was just a small example how custom router can be created and modified. Sky is your limit on options that you can use to customize your router to fit your needs.

Let’s just see the config.xml of my custom extension to see how this router is set to onestepcheckout module:

<frontend>
        <routers>
            <onestepcheckout>
                <use>myextension_myrouter</use>
                <args>
                    <modules>
                        <Mynamespace_Mymodule before="Idev_OneStepCheckout">Mynamespace_Mymodule_Onestepcheckout</Mynamespace_Mymodule>
                    </modules>
                </args>
            </onestepcheckout>
        </routers>

Current situation now is:
Onestepcheckout module rewrites some Magento Core routes so when user visit checkout/onepage URL, he gets onestepcheckout URL instead.

When I created the custom router like above, the user will still visit onestepcheckout route, but Controller executed will be different depending if isParty or not.

Since this is same URL either for Party and Regular checkout, we set custom routeNames in our router so we can have separate layout update handles in xml for each state (Party and regular) checkout.

I know this seems a little bit messy for one reading, but I hope somebody will find this post useful for own projects and extension integrations and customizations. Cheers.

Interested in hiring us?

Have a chat with us. You would be surprised how small changes can make your business even more successful.


3 comments

  1. After many hours of trying to figure out how to define the router inside config.xml, I googled and found this post, and it is the missing piece. I managed to get the custom router to work but I still need to define the custom router in frontend:

        <default>
           <web>
             <routers>
                <extendedcustomer_router>
                    <area>frontend</area>
                    <class>Scicom_ExtendedCustomer_Controller_Router</class>
                </extendedcustomer_router>
            </routers>
           </web>
        </default>
        <frontend>
            <routers>
                <extendedcustomer>
                    <use>extendedcustomer_router</use>
                    <args>
                        <module>Scicom_ExtendedCustomer</module>
                        <frontName>extendedcustomer</frontName>
                    </args>
                </extendedcustomer>
            </routers>
        </frontend>

    Afterwhich, it works perfect. Thanks !

  2. You don’t need to rewrite standard router, but rather create and register your own and then set it for your module.

  3. I want to rewrite Mage_Core_Controller_Varien_Router_Standard.php in custom module

    I follows above step but it fail to work.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.