Creating a module in Magento 2

This is the first of what I expect will be several blog posts on Magento 2 as  I learn the system.  I believe that one of the best ways to learn something is to write out what it is you are learning.  This forces you to think through the concepts and determine how to explain them to others.  In doing so you are forced to use terms that are familiar to describe this new thing.  This helps to solidify the concepts in your own mind, making it easier to remember.

But still, there are three caveats.  1) Magento 2 is not out yet (as I write this) so things may change.  2) As I write this I am learning it and so there might be inaccuracies. 3) The information gleaned here has not been passed through the core team and so it is my interpretation of what I see.

One of the most visible changes from Magento 1 is the lack of code pools.  No longer is there a core, community and local set of directories.  I liked that there was this separation between different types of code, but it didn’t functionally add anything.  So this is a change that I am ambivalent about.

The next change is that rather than storing all of your module discovery XML files in app/etc/modules they are now stored in the module’s etc directory.  The benefit of this is that the modules are now more self contained than they used to be.  This is a change that I am quite happy about.  And while there is no longer any scope code there still is loading precedence.  The order precedence is base (Magento), custom (Others) and base (You).  This is determined by the location of the module.xml file.  If it is in app/code/Magento… it is Magento.  If module.xml is under any other directory in app/code then it is put into the “custom” scope.  If module.xml exists in a directory under app/etc, such as app/etc/me/module.xml, it is put into the “base” scope.  The names of the scope are irrelevant, but what is important is the order; mage, custom, base.

The parsing of the module.xml files is done by a class called Magento\Config\Dom.  This class does not use SimpleXML like before, but rather uses DOMDocument.  An interesting thing about this change is that it is possible to reference configuration nodes by ID.

However, there is a problem.  In my IDE at least (Zend Studio), DOMDocument is not viewable in the debug variable view whereas SimpleXML was.  I expect that this might make debugging configuration merge errors a little more difficult because I can’t see what the resulting node looks like.  That said, the merged XML document is converted to an array so after the configuration merge is completed you will see what the config looks like.  Additionally, the module nodes are validated against an XSD located in lib/Magento/Module/etc/module.xsd.  Modules are then stored based on dependency and injected into the cache.

This is all done by a class called Magento\App\ObjectManager\ConfigLoader which is injected into a class called Magento\ObjectManager\ObjectManager which is a replacement for Mage::getModel().  That’s not quite true but, like I said about making references to what is known, it is true enough.

Unlike Magento 1 the module list is not stored in the global configuration.  It is stored in the ObjectManager in an object of type Magento\Module\ModuleList.  This means that you will not be querying the configuration for the module list you will need to define the module list as a dependency in your class.

What this also means is that having a config.xml does not “activate” a module, like in Magento 1.  This also hints that you do not need to do that damn /config/models/<name>/class stuff.  There is a LOT of stuff you can do via dependency that will replicate some of that but I will cover that in a later blog post on DI in Magento 2.  Once you get into area-based DI I think you’ll also be getting into some cool stuff.

So let’s create a module that simply echos “Hello World” in the content.

We will start with a module.xml file in app/code/Eschrade/HelloWorld/etc/.

<?xml version="1.0"?>
<config>
  <module name="Eschrade_HelloWorld" version="0.0.1" active="true" />
</config>

There are a few things different.  For one, the options are specified via XML attributes instead of XML nodes.  Secondly, the version is now required.

In an effort to stick to one topic we will output our “Hello World” via the “controller_action_predispatch” event so we don’t have to yet start talking about Invocation Chains, though we will need to talk about DI for a little bit.

To register an event we need to go to our app/code/Eschrade/HelloWorld/etc directory and create a new file called event.xml with the following content.

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <event name="controller_action_predispatch">
    <observer
      name="eschrade_helloworld_echo"
      instance="Eschrade\HelloWorld\Model\Observer"
      method="echoHello" />
  </event>
</config>

Gotta say I really like this new format compared to the old one.  The name is specified via an attribute and the node has a child called observer with attributes name, which is used to give a unique name to the observer, instance, which is the class name of your observer, and the method, which is the method that needs to be called.

The observer is nice and simple.  Put it in app/code/Eschrade/HelloWorld/Model/Observer.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace Eschrade\HelloWorld\Model;
 
class Observer
{
  protected $_response;
 
  public function __construct(
    \Magento\App\Response\Http $response
  ) {
    $this->_response = $response;
  }
 
  public function echoHello(\Magento\Event\Observer $observer)
  {
    $this->_response->appendBody('Hello World');
  }
}

In order for us to echo it properly we need to append it to the response object.  Prior to this we would need to call Mage::app()->getResponse->appendBody().  In this case we simple declared, through the constructor, that we needed the response object and placed it in a protected property of the class.  Then when the observer method is called we simply reference the property and get the following output.

Hello World<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8"/>

The underlying implementation to get this working is somewhat complex but I am quite pleased at how easy it was to get this up and running.

0 comments

Post Navigation

Web Analytics