Custom router in Magento

Custom router in Magento

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.

You made it all the way down here so you must have enjoyed this post! You may also like:

External database connection in Magento Damir Serfezi
Damir Serfezi, | 4

External database connection in Magento

Enabling Multi-part MIME Emails in Magento Tomislav Nikcevski
Tomislav Nikcevski, | 3

Enabling Multi-part MIME Emails in Magento

Pimcore Portlets Zoran Salamun
Zoran Salamun, | 0

Pimcore Portlets

7 comments

  1. I am generating dynamic url’s ends with ‘-s.html’. will the above code works for this url’s or what should be the code change for this url’s to work

    example urls
    1. example.com/aa/bbb-s.html
    2. example.com/cc/adfdk-s.html
    3. example.com/fdfdfdfdfj/djhfdjfdjhf-s.html

  2. Very informative article… I appreciate your Magento expertise… As I’ve recently been asked to do some Magento development I decided to do a bit of research on fundamental workflow and I can’t say I’m all too excited… After coming from working with more sophisticated implementations such Spring MVC, Spring Boot, DropWizard, Apache Wicket… Magento’s architecture feels like I’ve stepped backwards fifteen years…

  3. 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 !

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

  5. 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> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.

Tell us about your project

Drop us a line. We'd love to know more about your project.