Playing Around with Magento 2 Areas

This post shares some personal experiences writing a minimal Magento 2 module. I thought it may interesting to those who wanted to see a bit more about how modules are put together in Magento 2.

Disclaimer: This is not an attempt at “good module” design according to official Magento 2 coding standards – it is just result of some personal experimentation.

Background – I was trying to get more hands on experience writing my own module to implement a REST service. Nothing like using the product oneself. The first problem I hit was I was not sure the best way to check if the module I was developing was being loaded. So I decided to also write a simple module to display what modules have been loaded. There is a probably better way than this, but it was a good excuse to write another module. Because it was for diagnosing problems, I decided to do it as a new “area”. This is not what most developers would do. Most modules would use the Magento Layout framework for page rendering. In my case, I wanted to invoke the least possible code in the rest of the system, so I decided to introduce my own new area instead. (OK, so it was also partly an excuse just to see how hard it was to do!)

By default there are areas for the store front, administration, SOAP web services, and REST web services. SOAP and REST web services have URLs that start with /soap and /rest. The idea of areas is the processing of URLs is different per area. For example SOAP, REST, and store front requests are different enough do warrant different processing logic. I snuck at look at the web service module (Magento/Webapi) to work out what to do.

Here is the overall directory structure. I will go through these files one by one showing the full source code for each. I called my module AlanKent/ShowApplicationState, so all files reside under app/code/AlanKent/ShowApplicationState. All the following paths are relative to this directory.

ShowAppState

etc/module.xml

When creating a new module, the first file to create is the etc/module.xml file.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
    <module name="AlanKent_ShowApplicationState" schema_version="0" active="true">
    </module>
</config>

This file normally includes the names of other modules my module depends on. In this case, I purposely did not want to depend on any other modules, so my module.xml file was pretty empty. The main thing here is the module name.

etc/di.xml

The next thing was register the new area. This is done via configuration in the Dependency Injection framework di.xml file.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
 
    <type name="Magento\Framework\App\AreaList">
        <arguments>
            <argument name="areas" xsi:type="array">
                <item name="showapplicationstate" xsi:type="array">
                    <item name="frontName" xsi:type="string">showappstate</item>
                </item>
            </argument>
        </arguments>
    </type>
 
</config>

How this works is the <argument> element causes an another value to be added to the existing array of configuration values for the Magneto\Framework\App\AreaList class. In this case, the above adds a new area name called showapplicationstate with a front name of showappstate. The front name is what appears at the “front” of the URL. The area name is used internally to refer to the area in configuration files. (I purposely used slightly different names to help clarify which was which.)

etc/showapplicationstate/di.xml

Each area can also have a second di.xml file under a directory with same name as the area name. This file is loaded only if the area is referenced by the URL. This allows each area to have different configuration if required. In this case, the configuration needs to register a front controller for the new area. The REST area for example has a front controller that knows how to do URL lookups for REST based URLs. In this module, the controller ignores the URL and just returns the HTML directly. This is not typical for an area, but good enough for this example.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <preference for="Magento\Framework\App\FrontControllerInterface"
        type="AlanKent\ShowApplicationState\Controller\ShowApplicationStateFrontController" />
</config>

The above tells the DI framework to use an instance of the class AlanKent\ShowApplicationState\Controller\ShowApplicationStateFrontController whenever the front controller (Magento\Framework\App\FrontControllerInterface) is requested by the Dependency Injection framework. Plugging in a class in place of an interface declaration is how to provide an implementation for a SPI (Service Provider Interface).

Controller/ShowApplicationStateFrontController.php

Hmmm. ShowApplicationStateFrontController is not the shortest class name, but that it is not the focus of what I am trying to achieve.  Here is the full implementation of the front controller. I will explain it a section at a time below.

<?php

namespace AlanKent\ShowApplicationState\Controller;

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

class ShowApplicationStateFrontController implements \Magento\Framework\App\FrontControllerInterface
{
    /** @var \Magento\Framework\ObjectManager */
    protected $objectManager;

    /** @var \Magento\Framework\App\ResponseInterface */
    protected $response;

    /** @var \Magento\Framework\App\State */
    protected $appState;

    /** @var \Magento\Framework\App\AreaList */
    protected $areaList;

    /** @var \Magento\Framework\Session\Generic */
    protected $session;

    /** @var \Magento\Framework\Module\ModuleListInterface */
    protected $moduleList;

    /**
     * Initialize dependencies
     *
     * @param \Magento\Framework\ObjectManager $objectManager
     * @param \Magento\Framework\App\ResponseInterface $response
     * @param \Magento\Framework\App\State $appState
     * @param \Magento\Framework\App\AreaList $areaList
     * @param \Magento\Framework\Session\Generic $session
     * @param \Magento\Framework\Module\ModuleListInterface $moduleList
     */
    public function __construct(
        \Magento\Framework\ObjectManager $objectManager,
        \Magento\Framework\App\ResponseInterface $response,
        \Magento\Framework\App\State $appState,
        \Magento\Framework\App\AreaList $areaList,
        \Magento\Framework\Session\Generic $session,
        \Magento\Framework\Module\ModuleListInterface $moduleList
    ) {
        $this->objectManager = $objectManager;
        $this->response = $response;
        $this->appState = $appState;
        $this->areaList = $areaList;
        $this->session = $session;
        $this->moduleList = $moduleList;
    }

    /**
     * Dispatch application action
     *
     * @param RequestInterface $request
     * @return ResponseInterface
     */
    public function dispatch(RequestInterface $request)
    {
        $resp = "<html><body>\n";
        $resp .= "</body></html><h1>Application State</h1>\n";
        $resp .= "<table>\n";
        $resp .= "<tr><td>Session Name:</td><td>"
                   . htmlspecialchars($this->session->getName()) . "</td></tr>\n";
        $resp .= "<tr><td>Cookie Domain:</td><td>"
                   . htmlspecialchars($this->session->getCookieDomain()) . "</td></tr>\n";
        $resp .= "<tr><td>Cookie Path:</td><td>"
                   . htmlspecialchars($this->session->getCookiePath()) . "</td></tr>\n";
        $resp .= "<tr><td>Cookie Lifetime:</td><td>"
                   . htmlspecialchars($this->session->getCookieLifetime()) . "</td></tr>\n";

        $resp .= "<tr><td>Application State:</td><td>"
                   . htmlspecialchars($this->appState->getMode()) . "</td></tr>\n";

        $resp .= "<tr valign='top'><td>Areas:</td><td>";
        $resp .= "<table>";
        foreach ($this->areaList->getCodes() as $areaCode) {
           $resp .= "<tr><td>" . htmlspecialchars($areaCode) . "</td><td>/"
                       . $this->areaList->getFrontName($areaCode) . "</td></tr>\n";
        }
        $resp .= "</table>";
        $resp .= "</td></tr>\n";

        $resp .= "<tr valign='top'><td>Modules:</td><td>";
        foreach ($this->moduleList->getModules() as $moduleName => $module) {
            $resp .= $moduleName . "<br />\n";
        }
        $resp .= "</td></tr>\n";

        $resp .= "</table>\n";
        $resp .= "</body></html>\n";

        $this->response->setBody($resp);
        return $this->response;
    }
}

The first thing to look at is the constructor. It declares all the objects that the class wants to get access to. Each argument is saved away in a field for later use by the methods of the class. So where do all the objects needed by the constructor come from? Well, that is a part of the magic of the Dependency Injection framework. I only had to declare the constructor – the Dependency Injection framework worked out the dependencies for me (from other di.xml files) and supplied all the required objects automatically to the constructor.

The only other function in the class is the dispatch() function. Normally for an area this method is would dispatch the URL appropriately for for processing. For a REST request, the URL is matched against patterns, parameters are extracted from the URL, and the appropriate service implementation is called. For store front pages different logic is used. In my example, the code generates some simple HTML by pulling values out of the available context. For example, the code displays if Magento is in default, developer, or production mode. It also lists all the loaded areas and modules.

Calling the Module

On my local development environment, I used the following URL to access the new area: http:// 127.0.0.1/magento2/index.php/showappstate. Here ‘showappstate’ is the ‘front name for the area. This URL (with /magento2/index.php) is not how you would normally configure your production environment, but as I said, this was an experiment on my laptop. Example output is as follows.

ShowAppStateOutput

Conclusion

I wrote this post to share a personal experience in writing a minimal module that introduced a new area. It would be rare for an extension developer to introduce a new area in practice. Hopefully however this example gives a little more insight into how the Dependency Injection framework can be used to extend Magento 2. No changes were required outside of the module directory to add the new functionality. The module itself is pretty simple and crude – I wrote the module to better understand the minimum requirement to build a new module.

Oh, and be aware that the code base and configuration files may change before official release. So if you come across the post in the future, the code or configuration files might need some tweaking to work when the official Magento 2 release comes out.

6 comments

  1. I am impressed by the quality of information in this article. There are lot of useful information about it.

    1. Thanks, glad useful. (I almost thought this was spam until I noticed you do magento development!)

  2. Hi Allen,
    First of all thank you so much, I appreciate your blog, I take help from your blog for development of rest API, And created several Rest API,

    Now I want to login from external source in magento2.0, I did this in magento 1.9 but facing problem in magento2.0,
    and got the error “Area code not set: Area code must be set before starting a session.” Then i dig in the code and find, this is comming from class SessionManager
    \Magento\Framework\Session

    I think problem is of session start(may be wrong),

    My Magento1.9 code is below,

    require_once ‘app/Mage.php’;
    umask(0);
    $app = Mage::app(‘default’);

    Mage::getSingleton(‘core/session’, array(‘name’ => ‘adminhtml’));

    // supply username
    $user = Mage::getModel(‘admin/user’)->loadByUsername(‘admin’); // user your admin username
    //$role = Mage::getModel(‘admin/user’)->hasAssigned2Role($user);
    //$validate = Mage::getModel(‘admin/user’)->validate();
    echo ‘

    ';print_r($user->getData());exit;
    
    if (Mage::getSingleton('adminhtml/url')->useSecretKey()) {
      Mage::getSingleton('adminhtml/url')->renewSecretUrls();
    }
    
    $session = Mage::getSingleton('admin/session');
    $session->setIsFirstVisit(true);
    $session->setUser($user);
    $session->setAcl(Mage::getResourceModel('admin/acl')->loadAcl());
    Mage::dispatchEvent('admin_session_user_login_success',array('user'=>$user));
    
    It works fine,
    /****************************************************************************************************************/
    And Magento 2.0 code is as follows,
    
    require __DIR__ . '/app/bootstrap.php';
    
    class TestApp extends \Magento\Framework\App\Http implements \Magento\Framework\AppInterface
    {   
        public function launch()
        { //echo 'test---------------';exit;
    		$config = $this->_objectManager->create('\Magento\User\Model\User');
    		$backendmodel = $this->_objectManager->create('\Magento\Backend\Model\Url');
    		
    		
    $user = $config->loadByUsername('admin'); 
    if ($backendmodel->useSecretKey()) {
      $backendmodel->renewSecretUrls();
    }
    $Sessiondmodel = $this->_objectManager->create('\Magento\Backend\Model\Session');
    //$Sessiondmodel =  $this->_objectManager->create('\Magento\Framework\Session\Generic'); 
    //$State =  $this->_objectManager->create('\Magento\Framework\App\State'); 
    echo '
    ';print_r($State->getAreaCode());exit;
    //$Sessiondmodel->setIsFirstVisit(true);
    //$Sessiondmodel->setUser($user);
    //$Sessiondmodel->setAcl(Mage::getResourceModel('admin/acl')->loadAcl());
    //$eventmanager->dispatch('admin_session_user_login_success',array('user'=>$user));
    
    							
    							//return $prodarr;
    							
    				
        }
    
    
    
    
        public function catchException(\Magento\Framework\App\Bootstrap $bootstrap, \Exception $exception)
        {
            return false;
        }
    }
    1. I recall something about area names not being set when context not known. I will try to dig up my notes.

  3. http://magento.stackexchange.com/questions/96747/area-code-not-set-issue-in-custom-cli-commands-in-magento-2

    public function __construct(
    \Magento\Framework\App\State $state
    ) {
    $state->setAreaCode(‘frontend’); // or ‘adminhtml’, depending on your needs
    }

  4. Thanks for sharing this post, when i was stuck in creating controller, your guide helped me a lot, but for structure of module i followed this post, https://www.cloudways.com/blog/create-module-in-magento-2/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: