The Basics of Dependancy Injection in Magento 2

Are you wondering how to use Dependency Injection in Magento 2? Find out in this 4th article of our Magento 2 Development series. As well as this, Yoda (one of our certified Magento devs) explains why he uses Dependency Injection and why he stopped using Object Manager.

An introduction to Dependency Injection

Dependency Injection (or DI) is the method Magento designed to replace the Mage functionality from Magento 1. Rather than use a command like
“Mage::model(‘digitalsix/example’)
”, you specify the class or interface you require in your class constructor.

Magento 2 takes the information you provide about your required classes/interfaces and then uses the Object Manager to create an instance of each one if it doesn’t already exist, then provides them to the constructor for you to use. Between this process, and the fact that you can find examples of Magento 2’s core code calling the Object Manager directly, it can be very tempting to skip the DI process and just use the Object Manager to create instances of classes when you need them instead. However, this is bad practice and should be avoided.

There are various reasons why you shouldn’t use the Object Manager yourself, from prevention of proper validation to the simple fact that Magento say not to. It interferes with the proper use of the framework and can cause issues you’re not expecting down the line.

What does Dependency Injection do and how do I use it?

Basically, DI is a way for you to tell Magento what you need, and it’ll make that happen for you without you needing to go through all the setup process yourself. In a similar fashion to how Composer sorts out all of the dependencies for a package you’re installing, the Magento DI process sorts out all the dependencies for every object you need as well.

The typical way of using dependency injection is known as constructor injection. When you create a class, you can create a constructor for it. In that constructor, you can specify parameters with required types that should be sent to the class when instantiated. When Magento compiles the code, it checks what object types your class needs and then when it runs the code, it instantiates an object (or recalls the previously instantiated object if it already exists) and passes it as a parameter to your constructor.

Or to put it more simply: if you tell Magento what you need, Magento will give it to you. An example of constructor injection can be seen below:

public function __construct(
\Magento\Catalog\Api\ProductRepositoryInterface $productRepository
) { … }

Why request interfaces rather than classes?

As covered in our previous Magento 2 article, you should always be using and working with interfaces rather than specific classes where possible. This reduces incompatibility, and lets you focus on what the class does instead of how it’s implemented. This also allows you to take advantage of the other side of dependency injection which lets Magento determine which version of an interface it should be using.

For example, if you request Magento\Catalog\Model\ProductRepository, then that is all you will ever get, even if that is not the correct class to be using. Because you’ve specified that precise class, that’s all you’ll get.

However, if we’ve created a custom version of the Magento\Catalog\Api\Data\ProductInterface class, which requires a custom version of the Magento\Catalog\Api\ProductRepositoryInterface to use, then the Magento\Catalog\Model\ProductRepository class isn’t going to work. Worse, it’s going to break the website because it isn’t compatible with our custom implementation of the ProductInterface.

Instead, you should request Magento\Catalog\Api\ProductRepositoryInterface. Magento will then scan through all of the installed modules to determine what the preferred version is. In our example case, this would be DigitalSix\Catalog\Model\ProductRepository rather than
Magento\Catalog\Model\ProductRepository.

The way the preferred version is determined is by checking the di.xml files in each module. If you look at the etc/di.xml file in the Magento Catalog module you will see all of its default class preferences, including ProductRepositoryInterface:

<preference for=”Magento\Catalog\Api\ProductRepositoryInterface” type=”Magento\Catalog\Model\ProductRepository” />

Magento determines which preference takes priority based on which module is loaded last. This is determined by the sequence portion of the module.xml file. So in our example we have a later loading preference:

<preference for=”Magento\Catalog\Api\ProductRepositoryInterface” type=”DigitalSix\Catalog\Model\ProductRepository” />

If your code is set up to use the interface version of both ProductRepository and Product, then you will have no trouble handling the fact you’ve received very different implementations of the interfaces because all of the same methods defined in the interface are required to exist, accept the same types of parameters and return the same types of values.

What if I want a resource-heavy class, but only sometimes?

If you require an object which you expect to use a lot of resources but that you don’t always need within your class, it can be tempting to use the Object Manager to instantiate it only when it’s needed. But, there’s a better way!

You can use proxy classes to lazy-load the objects. Proxies are auto-generated by Magento 2 during the compile process, and only load the object when a method is called on it.

By manipulating the arguments passed to a class in the di.xmldi.xml file, you can specify that a proxy version of the class should be used instead of the normal version.

For the sake of example, let’s say that our custom DigitalSix product repository requires a class called DigitalSix\Catalog\Model\SlowIndexer but only in specific cases. The SlowIndexer takes up a lot of resources and we don’t want it loaded every time the product repository is used (which is a lot), so we want to use a proxy version instead. To do this, we add the following code to our di.xml file:

<type name=”DigitalSix\Catalog\Model\ProductRepository”>
<arguments>
<argument name=”slowIndexer” xsi:type=”object”>DigitalSix\Catalog\Model\SlowIndexer\Proxy</argument>
</arguments>
</type>

In our ProductRepository constructor, we inject the SlowIndexer as normal:

public function __construct(
…,
DigitalSix\Catalog\Model\SlowIndexer $slowIndexer
) { … }

Now when Magento creates an instance of the product repository, it uses a proxy instance of the SlowIndexer instead until the normal instance is needed at which point it loads the object.

Summary

Why use dependency injection? Because you get cleaner, better written code with long-term compatibility.
Why shouldn’t you use the Object Manager? Because it can introduce compatibility problems if misused and skips the proper type validation checks.

If you find you need to use the Object Manager to get objects within a template, then you should instead look at using view models to pass those objects instead. This is another implementation of dependency injection, which will let you specify the objects you require within your template.

If you found this article useful, look out for my next blog article which covers the basics on Magento areas.

GET IN TOUCH
Digital Six
Digital Six Ltd, Norloch House, 36 King's Stables Road, Edinburgh,
EH1 2EU
Partnerships