Many Magento 2 extensions, like blog modules, allow merchants to configure the URL of the page in the admin within the Stores -> Configuration
area. Developers can add this functionality by adding a custom Magento 2 Router class allowing the merchant to add a dynamic route in the Magento 2 admin.
Below will describe the steps necessary in order to allow for a dynamic route to be configured in the Stores -> Configuration
section.
Firstly, add the custom module’s registration.php
and module.xml
files.
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, '[Vendor]_[Module]', __DIR__ );
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="[Vendor]_[Module]" setup_version="1.0.0" /> </config>
Enable the module and upgrade the Magento 2 database by running the following commands from the Magento 2 root directory.
$ /path/to/php bin/magento module:enable [Vendor]_[Module] $ /path/to/php bin/magento setup:upgrade
Start with adding the system configuration-related files. Define the module’s system.xml
file that will allow the merchant to configure a custom URL from within the admin.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <tab id="[vendor]" translate="label" sortOrder="850"> <label>[Vendor] [Module]</label> </tab> <section id="[module]" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Custom Route</label> <tab>[vendor]</tab> <resource>[Vendor]_[Module]::[module]</resource> <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>General</label> <field id="route" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Route</label> </field> </group> </section> </system> </config>
Now define the ACL configuration for the section added within the system.xml
file.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd"> <acl> <resources> <resource id="Magento_Backend::admin"> <resource id="Magento_Backend::stores"> <resource id="Magento_Backend::stores_settings"> <resource id="Magento_Config::config"> <resource id="[Vendor]_[Module]::[module]" title="[Vendor] [Module]" translate="title" /> </resource> </resource> </resource> </resource> </resources> </acl> </config>
Generally, a ‘default’ custom route is defined which is combined with a controller class to handle a route if the merchant doesn’t define one in the admin.
For example, a blog module may use a blog
default route defined in the config.xml
along with a blog
route defined in routes.xml
.
In this example, define a default route used for the system configuration in the module’s config.xml
file.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> <default> <[module]> <general> <route>customroute</route> </general> </[module]> </default> </config>
In addition, add a routes.xml
file defining the default route.
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> <router id="standard"> <route id="customroute" frontName="customroute"> <module name="[Vendor]_[Module]"/> </route> </router> </config>
Add a Controller class used to handle the route.
<?php namespace [Vendor]\[Module]\Controller\Index; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\PageFactory; class Index extends \Magento\Framework\App\Action\Action { /** * @var PageFactory */ protected $resultPageFactory; /** * Index constructor. * @param Context $context * @param PageFactory $resultPageFactory */ public function __construct( Context $context, PageFactory $resultPageFactory ) { $this->resultPageFactory = $resultPageFactory; parent::__construct($context); } /** * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|\Magento\Framework\View\Result\Page */ public function execute() { $page = $this->resultPageFactory->create(); $page->getConfig()->getTitle()->set('Custom Route'); return $page; } }
Now we have the system configuration defined, along with a default value. So if the custom route isn’t changed from the default customroute
route, the Controller class will still be able to handle the route.
Next will be to add the Router class. First of all, the Router should be defined within the module’s di.xml
file within the routerList
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="customroute" xsi:type="array"> <item name="class" xsi:type="string">[Vendor]\[Module]\Controller\Router</item> <item name="disable" xsi:type="boolean">false</item> <item name="sortOrder" xsi:type="string">60</item> </item> </argument> </arguments> </type> </config>
Add the Router class, that should implement the interface, and subsequently contain a
match()
method.
In summary, the module
path of the route is checked again the route added in Stores -> Configuration
in the admin, and if it matches, set the module
, controller
and action
names of the request to customroute
, index
and index
respectively. This will allow the controller class defined above to handle the route.
<?php namespace [Vendor]\[Module]\Controller; use Magento\Framework\App\ActionFactory; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\RouterInterface; use Magento\Framework\DataObject; use Magento\Framework\Event\ManagerInterface as EventManagerInterface; use [Vendor]\[Module]\Helper\Data as CustomRouteHelper; class Router implements RouterInterface { /** * @var bool */ private $dispatched = false; /** * @var ActionFactory */ protected $actionFactory; /** * @var EventManagerInterface */ protected $eventManager; /** * @var CustomRouteHelper */ protected $helper; /** * Router constructor. * * @param ActionFactory $actionFactory * @param EventManagerInterface $eventManager * @param CustomRouteHelper $helper */ public function __construct( ActionFactory $actionFactory, EventManagerInterface $eventManager, CustomRouteHelper $helper ) { $this->actionFactory = $actionFactory; $this->eventManager = $eventManager; $this->helper = $helper; } /** * @param RequestInterface $request * @return \Magento\Framework\App\ActionInterface|null */ public function match(RequestInterface $request) { /** @var \Magento\Framework\App\Request\Http $request */ if (!$this->dispatched) { $identifier = trim($request->getPathInfo(), '/'); $this->eventManager->dispatch('core_controller_router_match_before', [ 'router' => $this, 'condition' => new DataObject(['identifier' => $identifier, 'continue' => true]) ]); $route = $this->helper->getModuleRoute(); if ($route !== '' && $identifier === $route) { $request->setModuleName('customroute') ->setControllerName('index') ->setActionName('index'); $request->setAlias(\Magento\Framework\Url::REWRITE_REQUEST_PATH_ALIAS, $identifier); $this->dispatched = true; return $this->actionFactory->create( 'Magento\Framework\App\Action\Forward' ); } return null; } } }
If you haven’t noticed from the above code, a Helper class is defined. This simply contains a method that will return the custom route defined in the Stores -> Configuration
area.
<?php namespace [Vendor]\[Module]\Helper; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Helper\AbstractHelper; use Magento\Framework\App\Helper\Context; use Magento\Store\Model\ScopeInterface; class Data extends AbstractHelper { const XML_PATH_CUSTOMROUTE_ROUTE = '[module]/general/route'; /** * @var ScopeConfigInterface */ protected $scopeConfig; /** * Data constructor. * * @param Context $context * @param ScopeConfigInterface $scopeConfig */ public function __construct( Context $context, ScopeConfigInterface $scopeConfig ) { parent::__construct($context); $this->scopeConfig = $scopeConfig; } /** * @return string */ public function getModuleRoute() { return $this->scopeConfig->getValue(self::XML_PATH_CUSTOMROUTE_ROUTE, ScopeInterface::SCOPE_STORE); } }
Lastly, a customroute_index_index.xml
layout file will be defined that will render a template with some content.
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Magento\Framework\View\Element\Template" name="customroute.default.template" template="[Vendor]_[Module]::customroute/default.phtml" /> </referenceContainer> </body> </page>
<div class="content"> <p><?php echo __('Here\'s some content showing regardless of the route configured.'); ?></p> </div>
And that’s it! Now whatever route you define in the admin, the Controller class will handle it and render the content from the template defined in the layout file.
Note: This article is based on Magento Open Source version 2.2.