About Dependency Injection in Magento 2

Mstislav Sergeev
6 min readNov 24, 2022

--

Dependency Injection in Magento 2

Dependency injection is a design pattern aimed to provide an object with all the dependencies needed for its work. This is a more efficient and advanced alternative to the inheritance, allowing you to reduce the coupling between classes and components.

This approach is based on the Dependency Inversion rule of SOLID principles. It involves the use of interfaces and abstract classes instead of detailed implementation. So the particular class must depend on abstractions, and the abstractions must not depend on implementation details.

Object Manager

Dependency injection in Magento 2 is handled by the designated class named ObjectManager (hereinafter referred to as OM), which is created when the application is initialized. OM is called automatically and it is an extremely bad practice to use it directly. If you do so, the dependencies cease to be explicit and the whole point of using Dependency Injection loses its meaning. However, there are exceptions out of this rule, which will be given below. Here is the code of the OM itself:

<?php
/**
* Magento object manager. Responsible for instantiating objects taking into account:
* - constructor arguments (using configured, and provided parameters)
* - class instances life style (singleton, transient)
* - interface preferences
*
* Intentionally contains multiple concerns for best performance
*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\ObjectManager;

class ObjectManager implements \Magento\Framework\ObjectManagerInterface
{
/**
* @var \Magento\Framework\ObjectManager\FactoryInterface
*/
protected $_factory;

/**
* List of shared instances
*
* @var array
*/
protected $_sharedInstances = [];

/**
* @var ConfigInterface
*/
protected $_config;

/**
* @param FactoryInterface $factory
* @param ConfigInterface $config
* @param array &$sharedInstances
*/
public function __construct(FactoryInterface $factory, ConfigInterface $config, &$sharedInstances = [])
{
$this->_config = $config;
$this->_factory = $factory;
$this->_sharedInstances = &$sharedInstances;
$this->_sharedInstances[\Magento\Framework\ObjectManagerInterface::class] = $this;
}

/**
* Create new object instance
*
* @param string $type
* @param array $arguments
* @return mixed
*/
public function create($type, array $arguments = [])
{
return $this->_factory->create($this->_config->getPreference($type), $arguments);
}

/**
* Retrieve cached object instance
*
* @param string $type
* @return mixed
*/
public function get($type)
{
$type = ltrim($type, '\\');
$type = $this->_config->getPreference($type);
if (!isset($this->_sharedInstances[$type])) {
$this->_sharedInstances[$type] = $this->_factory->create($type);
}
return $this->_sharedInstances[$type];
}

/**
* Configure di instance
* Note: All arguments should be pre-processed (sort order, translations, etc) before passing to method configure.
*
* @param array $configuration
* @return void
*/
public function configure(array $configuration)
{
$this->_config->extend($configuration);
}
}

The application uses the class constructor to get information about the object’s dependencies. OM fills the object with its dependencies defined in a special di.xml configuration file and places them into the class constructor during initialization. The exception is raised if the class is not found. The class arguments are collected recursively which means that dependencies are filled not only on the top level, but also on all lower ones (dependencies of the dependencies) till everything is loaded.

So, the basic responsibilities of the OM are:

  • Creating objects in factories and proxy classes
  • Returning single class instance
  • Automatic arguments initialization in class constructors

The di.xml file also plays its role:

  • OM is configured to work with dependencies there.
  • The interface (Service Contract) is wired to its implementation.
  • It allows you to override classes using preferences.
  • You can replace class arguments with your own or use proxies, virtual types, plugins, etc.

When is it allowed to use Object Manager?

There are cases when using OM is fair. For example:

  • if you use magic methods __wakeup() or __sleep();
  • in tests;
  • in factory classes and proxy classes.

What can be done when you need multiple class instances?

You should use factories. Factories exist to create objects.

In order to use it, add the desired class through the use keyword and add the Factory word at the end of its name like that:

use Magento\Framework\Controller\Result\RawFactory;

… then just add it into the constructor of your class:

public function __construct(RawFactory $resultRawFactory) {    
$this->resultRawFactory = $resultRawFactory;
}

Use the public factory method called create() to get the new instance of the class:

$resultRaw = $this->resultRawFactory->create();

We get a new object, as a result.

M2 creates all the objects necessary for its work when the production mode is enabled or on the fly in case of developer mode. All the files generated are placed in /generated/code folder. Please, see the example below:

<?php

namespace Magento\Framework\Controller\Result;

/**
* Factory class for @see \Magento\Framework\Controller\Result\Raw
*/
class RawFactory
{
/**
* Object Manager instance
*
* @var \Magento\Framework\ObjectManagerInterface
*/
protected $_objectManager = null;

/**
* Instance name to create
*
* @var string
*/
protected $_instanceName = null;

/**
* Factory constructor
*
* @param \Magento\Framework\ObjectManagerInterface $objectManager
* @param string $instanceName
*/
public function __construct(
\Magento\Framework\ObjectManagerInterface $objectManager,
$instanceName = '\\Magento\\Framework\\Controller\\Result\\Raw'
) {
$this->_objectManager = $objectManager;
$this->_instanceName = $instanceName;
}

/**
* Create class instance with specified parameters
*
* @param array $data
* @return \Magento\Framework\Controller\Result\Raw
*/
public function create(array $data = [])
{
return $this->_objectManager->create($this->_instanceName, $data);
}
}

The generated class uses the object manager, as you can see from the code above. It is allowed in factory classes as you already know.

What is the preference?

  1. The preference is the interface (Service Contract) wiring to the particular class. As mentioned above, it allows you to stick to Dependency Inversion principle to reduce coupling.
  2. The way to override the particular class. There are cases when we want to replace some basic class with our own implementation. To achieve that, we can use preferences. Thus, it is not very good approach and better to use plugins as much as possible instead of the overriding the whole classes.

This is how it looks:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Framework\Search\Adapter\OptionsInterface"
type="Magento\CatalogSearch\Model\Adapter\Options"/>
</config>

for attribute value is the class we are overriding, and the type attribute value is the class we are replacing.

What is the Proxy?

The Proxy is a design pattern that involves creating a surrogate for an object and controlling access to it.

This approach is used if the object is too “heavy”. It allows to load it only upon need (lazy loading), thereby speeding up the application.

In order to apply it, add some configuration to di.xml file. For example:

<type name="Magento\Framework\DataObject\Copy\Config">
<arguments>
<argument name="dataStorage" xsi:type="object">Magento\Framework\DataObject\Copy\Config\Data\Proxy</argument>
</arguments>
</type>

The <type> node specifies the class where we pass future proxy object as an argument. Then, we wire the argument name (the name attribute) with the actual class name ending with the word \Proxy at the end.

Changes are made only in the di.xml configuration file. You don’t have to do anything in the class. M2 will generate all necessary classes and put them into the /generated folder by itself.

dataStorage argument. No changes needed inside the class

What is the Virtual Type and the Type?

The Virtual Type in M2 is the approach which allows you to create a copy of the existing class with custom constructor arguments.

The “virtual” class has all properties and methods of the original class, but with custom constructor elements. The original class continues to function without any changes, i.e. the “old” class can be used as before. The Virtual Type is defined in the di.xml configuration file:

<virtualType name="layoutFileSourceBase" 
type="Magento\Framework\View\File\Collector\Base">
<arguments>
<argument name="subDir" xsi:type="string">layout</argument>
</arguments>
</virtualType>

You can define the virtual type name in the <virtualType> node under the name attribute. The type attribute contains original class.

The <arguments> node lists the argument values we want to replace with our own. So in our case the subDir argument holds the value layout which type is string.

Then this virtual type can be connected to some class through the same di.xml in the <type>:

<type name="Magento\Framework\View\File\Collector\Decorator\ModuleOutput">    
<arguments>
<argument name="subject" xsi:type="object">layoutFileSourceBase</argument>
</arguments>
</type>

Unlike Virtual Type, Type adds arguments to the real class.

This is how Dependency Injection is handled in Magento 2 and how to use it. This approach simplifies the development process and structures the application.

--

--