Injecting dependencies properly in Magento 2

Magento 2 offers a great way of loosely coupling modules together, by using the concept of Dependency Injection with a bit of configuration flavor. When you for instance want to inject yourself with a certain class, you can inject yourself with an instance of the interface by adding this to your constructor. Simple as that. However, make sure that one dependency is not injected twice.

Example: Injecting $scopeConfig

One example for this is when injecting an instance of the Magento\Framework\App\Config\ScopeConfigInterface interface which allows you to fetch values from the System Configuration tree. To do this in your helper class, you might create a class like the following. Note that this example is WRONG:

namespace Yireo\Example\Helper;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    protected $_scopeConfig;

    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
    ) {
        parent::__construct($context);
        $this->_scopeConfig = $scopeConfig;
    }

    public function isEnabled()
    {
        return (bool) $this->_scopeConfig->getValue('yireo/example/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
    }
}

Dissecting the class structure

Let's go over this a bit: First of all, the namespace is declared which allows our Data class to live in that namespace. Next, we extend from the generic AbstractHelper. In our constructor, we want to inject an instance of the ScopeConfigInterface interface, so we inject ourselves with this instance.

However the AbstractHelper class has also such a constructor, that requires a Context. So we need to duplicate the constructor arguments of the parent class, and our own constructor arguments to it. The $context variable is only injected to pass it to the parent constructor.

Using the $scopeConfig

So using DI, we can assign an instance of ScopeConfigInterface to a variable of our choice and to make it useful in our class, we can create an internal variable $this->_scopeConfig which is then used in our example isEnabled() class.

To be complete: The ScopeConfigInterface interface is mapped to an actual class using a DI preference. The enabled parameter is actually created through a system.xml file. The constant ScopeInterface::SCOPE_STORE is more-or-less hardcoded. It shows that constants defy the principle of loose coupling.

What is wrong?

There is nothing wrong code-wise with the example above. However, the flaw is that we have injected both $context and $scopeConfig, while actually $context already contains something similar to $scopeConfig. If you dive into the parent constructor (so AbstractHelper), you can quickly discover that there is already an instance of ScopeConfigInterface encapsulated in the $context variable, and this instance is assigned to $this->scopeConfig.

With the example above, we have two variables $this->scopeConfig and $this->_scopeConfig doing the exact same thing!

Correct definition

Once you have learned this, you can simply remove the entire DI and simply use the $this->scopeConfig variable instead. It cleans up the class bigtime.

namespace Yireo\Example\Helper;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    public function isEnabled()
    {
        return (bool) $this->scopeConfig->getValue('yireo/example/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
    }
}

Ofcourse it might still be that you need to inject other objects in your constructor, so for that the earlier example was nice.

Read your parent

Lesson: Make sure to inspect the parent constructor or possibly the parent of the parent and so on. It might be that something is injected elsewhere. Only inject that what you really need.