Request Flow in Magento 2

  • Index.php
  • Bootstrap
    • Bootstrap::run()
  • App
    • App:launch()
  • Routing
    • FrontController::dispatch()
    • Router::match()
  • Controller Processing
    • Controller::execute()
  • Rendering
    • View::loadLayout()
    • View::renderLayout()
  • Flushing Output
    • Response::sendResponse()

If you are familiar with Magento 1.x, you should be able substantial similarities in the request flow in Magento 2 is.

Now Magento 2 is built on composer the request flow is slightly different, however the fundamentals are almost the same regarding request flow.

Initiation Phase in Magento 2

As always the entry point for Magento 2 is in the root level index.php. Another entry point accessible to us are cron.php.

The entry point for Magento now looks like this:

    $bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
/** @var \Magento\Framework\App\Http $app */
$app = $bootstrap->createApplication('Magento\Framework\App\Http');
$bootstrap->run($app);

This is important to remember if you ever need to create your own entry point, for debugging or running quick queries.

There are two index.php files in Magento:

  • /var/www/magento2/index.php - Development
  • /var/www/magento2/pub/index.php - Production

Routing Magento 2

A router is tasked with taking a pretty SEO friendly URL path and converting it to a Magento 2, setting any parameters available to the request and identifying the appropriate controller to handle the request.

A big difference with controller classes now in Magento is rather than having an actionMethod() for a single url, you now have an action class. Your action class should contain an execute() method which handles the page rendering and output.

URL’s are the same as in Magento 1.x as in you /frontName/actionPath/action/param1/value1/param2/value2.

The method Magento\Framework\App\Router\Base::matchAction() is very insightful section into how this works:

    /**
    * Going through modules to find appropriate controller
*/
$currentModuleName = null;
$actionPath = null;
$action = null;
$actionInstance = null;

$actionPath = $this->matchActionPath($request, $params['actionPath']);
$action = $request->getActionName() ?: ($params['actionName'] ?: $this->_defaultPath->getPart('action'));
$this->_checkShouldBeSecure($request, '/' . $moduleFrontName . '/' . $actionPath . '/' . $action);

foreach ($modules as $moduleName) {
    $currentModuleName = $moduleName;

    $actionClassName = $this->actionList->get($moduleName, $this->pathPrefix, $actionPath, $action);
    if (!$actionClassName || !is_subclass_of($actionClassName, $this->actionInterface)) {
        continue;
    }

    $actionInstance = $this->actionFactory->create($actionClassName);
    break;
}

Front Controller

The front controller gathers routes and selects a controller using DI. Although Magento 2 still has rewrites, pretty url’s, these are gathered elsewhere. The FrontController class only has one method and implements the FrontControllerInterface interface.

Routing Mechanism

Here are the 5 routers in Magento and the order in which they get fired in:

  • Base Router
    • Magento\Core\App\Router\Base
  • CMS Router
    • Magento\Cms\Controller\Router
  • URL Rewrite Router
    • Magento\UrlRewrite\Controller\Router
  • Design Editor
  • Magento\DesignEditor\Controller\Varien\Router\Standard
  • Default Router (404 Router)
    • Magento\Framework\App\Router\DefaultRouter

An example of routing a non-standard router can be found in Magento\Cms\Controller\Router.php:

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

Each request is sent through the series of routers, it uses the Router::match() method as a flag, if it’s false we continue to the next router, if it’s true we run Controller::dispatch($action).

You can add your own custom router by adding it to Magento\Framework\App\RouterList.

Magento 2 checks the the assigned list of modules, foreach module it builds a potential action class and then checks whether the action class exists; if it exists it returns the class, else it will repeat.

Controller Architecture

As mentioned before, we now only have one action per controller Action Class, an action class should contain:

  • execute() method
  • Constructor where you set up you DI (Dependency Injection)

Action controllers extend Magento\Framework\App\Action\Action which in turn extends Magento\Framework\App\Action\Abstract, the abstract class implements the \Magento\Framework\App\ActionInterface.

The ActionInterface class only contains one method:

  • public function execute();

The Action\Abstract class contains the request and response objects, you will set these up using parent::__construct() (DI), as before the methods are:

  • getRequest()
  • getResponse()

The Action\Action class contains the dispatch() method, this is the method that the router will call; this in turn calls the execute() method.

The Catalog and Sales modules inheritance is slightly different in that they extend a class which extends Action\Action.

Admin and Frontend Controllers

As with Magento 1.x admin controllers are protected using ACL. Rather than having an adminhtml folder like in Magento 1.x we now have a backend folder.

Magento backend controllers extend the Magento\Backend\App\Action extends \Magento\Backend\App\AbstractAction, this then extends the \Magento\Framework\App\Action\Action class mentioned earlier.

The \Magento\Backend\App\AbstractAction rewrites the dispatch() method, the main thing to note is that the new dispatch() calls _isAllowed(). If you’re coming from a Magento 1.x background you should be familiar with this method by now. The _isAllowed() method is very important, it is the method which tells Magento whether the current user is aloud on the current admin route.

    /**
 * @return bool
 */
protected function _isAllowed()
{
    return $this->_authorization->isAllowed(self::ADMIN_RESOURCE);
}

You will need to implement the _isAllowed() method within your admin action class.

Changes the backend AbstractAction class implement are as follows:

  • Extra DI
  • Rewritten dipatch()
  • _isAllowed() method.
  • Rewritten redirect() method
  • Rewritten forward() method
  • Extra auxilary methods
    • _getSession()
    • _addBreadcrumb()
    • _addJs()
    • _addContent()
    • _addLeft()
    • _getUrl()

Yet again, these should be familiar if you’re coming from Magento 1.x.

# Steps in debugging:

  • Check if the frontName is correct
  • Check the list of available module.s
  • Check the path name foreach module

# Creating a controller

To create a controller you must create a etc/{area}/routes.xml file within your project, create an action class with the execute() method, then of course WRITE YOUR TESTS.

The two areas are:

  • frontend
  • adminhtml

Your routes.xml file should look like this:

    <?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="{area_id}">
        <route id="{module_name}" frontName="{front_name}">
            <module name="{Namespace_Modulename}" />
        </route>
    </router>
</config>

The two area_id’s are:

  • standard
  • admin

Earlier we mentioned how the url is created from 3 sections (frontName, ActionPath, Action), here is how they relate to your controllers:

  • Front Name
    • Usually your module name, defined in XML
  • ActionPath
    • Sub-folder of the Controller folder of the module
  • Action
    • The Controller Class
comments powered by Disqus