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.
About the author
Jisse Reitsma is the founder of Yireo, extension developer, developer trainer and 3x Magento Master. His passion is for technology and open source. And he loves talking as well.