Magento 2, Magento

Custom Router in Magento 2

Custom Router In Magento 2

Hello Everyone, welcome to my blog post. Today, I would like to share some of my findings on the custom router in Magento 2. In our development lifecycle, we face the situation where we need to create a custom URL for our module or we need to modify the route to match the requirement, in this situation we need to create a custom router in Magento 2. Let’s say the client asks us to create a product URL where we need to add a date or we may need to add a location. So, this kind of requirement can be fulfilled by custom Routers. Let’s jump to the coding part and create a custom router for our module. 

To create a custom router we are going to follow the below steps. 

  1. Create di.xml file
  2. Create Router Class
  3. Create Route using routes.xml 
  4. Create a frontend controller

Create Di.XML File

To create a custom route,  we need to add our custom router in RouterList (Magento\Framework\App\RouterList) and then we need to pass our custom router class as an argument. 

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <type name="Magento\Framework\App\RouterList">
       <arguments>
           <argument name="routerList" xsi:type="array">
               <item name="customrouter" xsi:type="array">
                   <item name="class" xsi:type="string">MyFirstModule\Mymodule\Controller\CustomRouter</item>
                   <item name="disable" xsi:type="boolean">false</item>
                   <item name="sortOrder" xsi:type="string">22</item>
               </item>
           </argument>
       </arguments>
   </type>
</config>

Where as customroute is our custom router name which we will use in URL, argument has other values like disable to enable or disable your router and sortOrder to set the priority or loading priority of your router class. 

Create Router Class

Our next step is to create our router class, this class must implement from RouterInterface (\Magento\Framework\App\RouterInterface) which provides a match() method to write our custom logic. 

Before moving ahead I would like to give you a brief idea about how Magento finds that match() method. The following code snippet will help to understand this functionality. At FrontController(Magento\Framework\App\FrontController) there is dispatch method, during bootstrapping Magento call dispatch method from this controller and get the list of available router and call its match() if match() method return Response(RequestInterface) then Magento process that response or else keep looing for another route match. 

public function dispatch(RequestInterface $request)
{
   \Magento\Framework\Profiler::start('routers_match');
   $this->validatedRequest = false;
   $routingCycleCounter = 0;
   $result = null;
   while (!$request->isDispatched() && $routingCycleCounter++ < 100) {
       /** @var \Magento\Framework\App\RouterInterface $router */
       foreach ($this->_routerList as $router) {
           try {
               $actionInstance = $router->match($request);
               if ($actionInstance) {
                   $result = $this->processRequest(
                       $request,
                       $actionInstance
                   );
                   break;
               }
           } catch (\Magento\Framework\Exception\NotFoundException $e) {
               $request->initForward();
               $request->setActionName('noroute');
               $request->setDispatched(false);
               break;
           }
       }
   }
   \Magento\Framework\Profiler::stop('routers_match');
   if ($routingCycleCounter > 100) {
       throw new \LogicException('Front controller reached 100 router match iterations');
   }
   return $result;
}

Note: If your router name and request URL consist of the same name then you will get the following error message “Front controller reached 100 router match iterations”. To avoid that, keep the router name different from your request URL name

Custom Router

<?php

namespace MyFirstModule\Mymodule\Controller;

use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\ActionFactory;
use Magento\Framework\App\ResponseInterface;

class CustomRouter implements \Magento\Framework\App\RouterInterface
{
   /**
    * @var ActionInterface
    */
   protected $actionFactory;
   /**
    * @var ResponseInterface
    */
   protected $response;

   /**
    * CustomRouter constructor.
    *
    * @param ActionFactory     $actionFactory
    * @param ResponseInterface $response
    */
   public function __construct(ActionFactory $actionFactory, ResponseInterface $response)
   {
       $this->actionFactory = $actionFactory;
       $this->response      = $response;
   }

   /**
    * @param RequestInterface $request
    *
    * @return false|ActionInterface
    */
   public function match(RequestInterface $request)
   {
       if ($request->getModuleName() === 'mymodule') {
           return;
       }
       $identifier = trim($request->getPathInfo(), '/');
       if (strpos($identifier, 'customrouter') !== false) {
           $request->setModuleName('mymodule')->setControllerName('index')->setActionName('index');
       } else {
           return;
       }

       return $this->actionFactory->create(
           'Magento\Framework\App\Action\Forward',
           ['request' => $request]
       );
   }
}

Routes.XML

Our next step is to create a routes.xml file in our <moduleDir>/etc/frontend directory and define our route.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
   <router id="standard">
       <route id="mymodule" frontName="mymodule">
           <module name="MyFirstModule_Mymodule"/>
       </route>
   </router>
</config>

Create a frontend controller

Let’s create our frontend controller to serve our request, in this controller we are just sending ResultFactory::TYPE_PAGE as a response.

<?php

namespace MyFirstModule\MyModule\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;

class Index extends Action
{
   /**
    * Index constructor.
    *
    * @param Context $context
    */
   public function __construct(Context $context)
   {
       parent::__construct($context);
   }

   /**
    * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface
    */
   public function execute()
   {
       return $this->resultFactory->create(ResultFactory::TYPE_PAGE);
   }
}

Note: One thing to note here is, from Magento 2.4 onwards you don’t need to use Magento/Framework/App/Action/Action as a parent class instead use Magento/Framework/App/ActionInterface.

We will discuss more on the Decomposition of Magento Controllers in the next blog. Please let me know your thoughts about this blog through the comment section. If you need any help or do you have any suggestions on this please feel free to write me an email on my email id admin@asolutions.co.in. Till next time Happy Coding 🙂 

Tagged , , ,

Leave a Reply

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

*

code