(Basic) Configuring the Magento 2 Dependency Injection Container

My previous blog post on the DIC (Dependency Injection Container) in Magento 2 covered just some basics of using the Magento 2 DIC.  The purpose of that post was to, perhaps, make you less apprehensive about using DI combined with the DIC in Magento 2.  However, in this post I want to go a little deeper into the DIC, implemented via the Magento\ObjectManager\ObjectManager class, and talk about how to configure it.

Configuration for the DIC is done in each module’s etc/di.xml file or etc/<area>/di.xml.  Because you can split DIC configuration based on the area this tells you that the /config/<area> naming stuff is over; which I applaud.  The configuration file has the standard /config base node, but then can have child nodes that are named “type”, “preference” or “virtualType”.  We will focus on “type” and “preference” here.

Type

The first configuration type we will look at is the “type” element.  This is used to give the DIC instructions on configuring the object that has been requested.  There are several instructions that can be given be we are going to focus, for the time being, on the “param” setting.  We will start by creating a new module called Eschrade\HelloWorld.

In that module we are going to create a class called Eschrade\HelloWorld\Model\Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Eschrade\HelloWorld\Model;
 
class Test
{
  protected $message;
 
  public function __construct($message)
  {
    $this->message = $message;
  }
 
  public function getMessage()
  {
    return $this->message;
  }
}

In order for us to get an instance of that class we need to call the DIC’s create() method.  In the previous blog post we called get().  We will get to that.

So how do we fulfill the requirement for the $message constructor argument?  The second parameter in the DIC’s create() method is that of arguments.  In other words, via the create() method you can inject your own data into the class.

1
2
3
4
5
6
7
8
9
10
11
12
use Magento\App\ObjectManagerFactory;
require_once 'app/bootstrap.php';
 
$locatorFactory = new ObjectManagerFactory();
$om = $locatorFactory->create(__DIR__, $_SERVER);
$test = $om->create(
  'Eschrade\HelloWorld\Model\Test',
  array(
    'message' => 'Hello World'
  )
);
echo $test->getMessage();

When we run this code we get

Hello World

Exactly what we placed in the argument.

But what if there are options you want to pass into the constructor that the classes with the dependency wouldn’t know about.  Some examples could be configuration strings, cache prefixes, template values and so on.  Requiring a class with a dependency to know how to fulfill one of the dependencies of its dependency would be a pain to manage.  To manage such things we now need to look at an example di.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <type name="Eschrade\HelloWorld\Model\Test">
    <param name="message">
      <value>Hello World</value>
    </param>
  </type>
</config>

One of the things you might have gotten used to in Magento 1 was arbitrarily throwing XML in wherever you want it.  That is not the case with Magento 2, particularly when it comes to the DIC.  In this case <type> is a node that has a certain definition (it can be found in lib/Magento/ObjectManager/etc/config.xsd).  This node has certain attributes and child nodes that it can have.  One attribute is “name” which tells the DIC the class name that this configuration element is referring to.  This is the fully qualified class name.

As a child node, one of the options is <param> (another is <instance> which we will not look at here).  It also requires a parameter called name which must match the name of the constructor argument, in this case “message”.  <value> is also a required node name and it contains the value of the string that will be passed in to the constructor.

We will now change our code a little to call get() instead of create() on the DIC.

1
2
3
4
5
$locatorFactory = new ObjectManagerFactory();
$om = $locatorFactory->create(__DIR__, $_SERVER);
 
$test = $om->get('Eschrade\HelloWorld\Model\Test');
echo $test->getMessage();

When we run this code we get

Hello World

Congratulations, you have now created a DIC configuration file.

Preference

A big part of using a DIC is the dependence on an interface to define the requirements of a given class.  This allows you to work on various implementation components in parallel while defining what it is this class will require.  Then, as part of unit testing, the interface can be mocked and provide sample data without depending on a concrete implementation of the interface.

So let’s change our Test model class to require an interface for its constructor.  We will call it Message and it will look like this.

1
2
3
4
5
6
namespace Eschrade\HelloWorld\Model;
 
interface Message
{
  public function getMessage();
}

Our Test model will now look like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Eschrade\HelloWorld\Model;
 
class Test
{
  protected $message;
 
  public function __construct(Message $message)
  {
    $this->message = $message;
  }
 
  public function getMessage()
  {
    return $this->message->getMessage();
  }
}

If we were to run this code right now we would get an error stating that the DIC could not instantiate an instance of Message because it is an interface.  So we need to do two things.  We need to define a concrete instance of that class and then tell the DIC to use it instead of the interface.

1
2
3
4
5
6
7
8
9
namespace Eschrade\HelloWorld\Model;
 
class HelloWorld implements Message
{
  public function getMessage()
  {
    return 'Hello World';
  }
}

And now our di.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <preference
    for="Eschrade\HelloWorld\Model\Message"
    type="Eschrade\HelloWorld\Model\HelloWorld" 
  />
</config>

When we run the code

1
2
$test = $om->get('Eschrade\HelloWorld\Model\Test');
echo $test->getMessage();

We get

Hello World

Instance Parameters

As nice as that was it is not really feasible to have such global definitions all of the time.  And so this time we will use the <type> element to declare HelloWorld as being the proper implementation of Message.  Because all of the code has been written all we need to do is change di.xml.

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <type name="Eschrade\HelloWorld\Model\Test">
    <param name="message">
     <instance type="Eschrade\HelloWorld\Model\HelloWorld" />
    </param>
  </type>
</config>

When we run our test code we get the following output.

Hello World

Post Navigation

Web Analytics