The Basics of Dependency Injection in Magento 2
Vinh Jacker | 11-11-2024
Dependency Injection is a unique design pattern that implements control inversion and provides the ability to follow the principle of dependency inversion. It is a technique that enables loose coupling. A number of the latest software application frameworks support Dependency Injection including TypeScript, Spring, Google Guice, Microsoft Managed Extensibility Framework (MEF), etc.
In Magento 2, one of the most significant changes is the use of Dependency Injection. With this design pattern, the codebase of Magento 2 has changed a lot, and many new things have been introduced. And now it has the same functionality as provided by the Mage class in Magento 1.
In this article, I will explain the basics of Magento 2 Dependency Injection in Magento 2 to help those who are new to this topic.
What is Dependency Injection?
Dependency is the object which is required by the class from an external source and injection is the method of passing that particular dependency to the dependent class. For example, if you have a class that fetches some data by using Magento 2 Observer Class, we can say that your class has a dependency on that Magento 2 Observer Object.
Still confused? Take a look at this fantastic example by John Munsch, who has explained dependency injection to a 5-year-old:
“When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open or get something Mommy or Daddy doesn’t want you to have. You might even be looking for something we don’t even have or which has expired. What you should be doing is stating a need, “I need something to drink with lunch,” and then we will make sure you have something when you sit down to eat.”
It means that the collaborating classes (5-year-old) should rely on the foundation classes (parents) to provide the desired results. Dependency Injection is a win-win situation for everyone, it passes the object to the dependent class, rather than allowing the dependent class to build or find the object from scratch.
Key Concepts of DI in Magento 2
1. Constructor Injection
Constructor Injection: This is the most common form of dependency injection. Dependencies are passed into a class via its constructor.
Example:
class MyClass
{
protected $dependency;
public function __construct(\Namespace\Module\ClassName $dependency)
{
$this->dependency = $dependency;
}
}
- Preference: Preferences allow you to define a class that should be used when another class or interface is requested.
Example (in di.xml
):
<preference for="Magento\Catalog\Api\ProductRepositoryInterface" type="Vendor\Module\Model\CustomProductRepository" />
- Virtual Types: Virtual types are a way to configure different instances of a class with varying arguments without modifying the class.
Example:
<virtualType name="Vendor\Module\Model\CustomModel" type="Magento\Framework\Model\AbstractModel">
<arguments>
<argument name="data" xsi:type="array">
<item name="customData" xsi:type="string">Some Value</item>
</argument>
</arguments>
</virtualType>
- Factory Classes: Factories allow you to create an object on-demand without hardcoding dependencies.
Example:
php Copy this code:
protected $objectFactory;
public function __construct(\Namespace\Module\Model\MyClassFactory $objectFactory)
{
$this->objectFactory = $objectFactory;
}
public function createObject()
{
$object = $this->objectFactory->create();
return $object;
}
- Proxy Classes: Proxies are used for lazy loading, meaning that an object is only instantiated when it’s needed, reducing overhead when objects are not always used.
2. Setter Injection
Setter injection involves injecting dependencies through setter methods. In this approach, the class provides setter methods that allow you to set the dependencies after the object has been created. Here’s an example:
class MyClass
{
private $dependency;
public function setDependency(Dependency $dependency)
{
$this->dependency = $dependency;
}
public function someMethod()
{
// Use the dependency
$this->dependency->doSomething();
}
}
- Injectable and Non-Injectable Objects: In Magento, certain objects (like models or collections) are considered non-injectable and should be instantiated via factories, while injectable objects (services, helpers, etc.) can be injected via DI.
di.xml Configuration
The di.xml
file is used to configure DI and contains definitions for preferences, virtual types, and other DI-related configurations. For example:
xml Copy this code:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\SomeModule\Api\SomeInterface" type="Vendor\Module\Model\CustomClass" />
</config>
In summary, Magento 2’s DI framework promotes loose coupling by handling class dependencies through constructors and configuration files like di.xml.
Dependency Inversion Principle
In order to reduce code dependencies, it’s highly recommended to follow the dependency inversion principle as well as use abstractions in your site code. It means that you shouldn’t let your high-level classes work directly with low-level classes. Instead, just use their interfaces.
By using your code interfaces, you can minimize the incompatibility risks when Magento modifies the underlying implementation of these interfaces. This method also enables you to focus on what the class does rather than how it is implemented.
As the Magento codebase follows the dependency inversion principle, you could use the di.xml file to connect your custom implementation of a Magento interface to a related class or service.
How Important Is Magento 2 Dependency Injection?
In general, Magento promotes Dependency Injection as a preferred approach for better code modularity, testability, reusability, flexibility, and so on. It helps developers build robust and maintainable applications while ensuring a high level of code quality and performance:
-
Modular and Testable Code: Dependency Injection helps in creating modular and loosely coupled code. By injecting dependencies instead of directly instantiating the within a class, it becomes easier to replace or mock dependencies during testing. This improves code maintainability and enables more efficient unit testing.
-
Code Reusability: With Magento 2 Dependency Injection, dependencies can be shared and reused across multiple classes. Instead of duplicating code to create instances of dependencies within different classes, dependencies can be injected and shared, reducing redundancy and improving efficiency.
-
Flexibility and Scalability: Dependency Injection allows for flexibility in managing dependencies. It becomes easier to switch or modify dependencies without making significant changes to the code. This promotes scalability as the system can easily accommodate changes or additions to the dependencies as the application evolves.
-
Decoupling of Components: Magento 2 Dependency Injection helps in decoupling components by removing direct dependencies between classes. This promotes the Single Responsibility Principle (SRP) and allows each class to focus on its own functionality, making the codebase more modular and easier to understand and maintain.
-
Magento encourages the use of Dependency Injection over direct use of Object Manager: In fact, Object Manager also offers quite same service to Dependency Injection. However, in spide of the benefit in less codes to write, Object Manager doesn’t follow the Develop Processes of Magento 2, and it even creats the unrequired hidden dependencies. Knowing what the code depends on is a better approach than facing hidden deprendencies in the code. That is the reason why the structure prefers Magento 2 Dependency Injection
Why do people prefer Magento 2 Dependency Injection to Over Object Manager?
Although the create () method of Object Manager can create your class’s objects, get, and pass the value for constructor parameter from di.xml files, we don’t recommend using it.
Object Manager goes against the objective of Dependency Injection. Even though Object Manager requires less code, it doesn’t follow the M2 Development Processes. Moreover, it creates hidden dependencies that are not required.
On the other hand, Dependency Injection has no hidden dependencies in the code. That’s why it’s a better choice.
Step-by-step Dependency Injection Examples in Magento 2
Constructor Injection
1. Define the dependency in the class constructor
namespace VendorModuleModel;
class MyClass
{
protected $_dependency;
public function __construct(
VendorModuleDependencyMyDependency $dependency
) {
$this->_dependency = $dependency;
}
}
2. Inject the dependency into the class
namespace VendorModuleDependency;
class MyDependency
{
public function doSomething()
{
// Do something
}
}
3. Use the injected dependency in your class
namespace VendorModuleModel;
class MyClass
{
protected $_dependency;
public function __construct(
VendorModuleDependencyMyDependency $dependency
) {
$this->_dependency = $dependency;
}
public function doSomething()
{
$this->_dependency->doSomething();
}
}
Setter Injection
1. Define the setter method in the class
namespace VendorModuleModel;
class MyClass
{
protected $_dependency;
public function setDependency(VendorModuleDependencyMyDependency $dependency)
{
$this->_dependency = $dependency;
}
}
2. Inject the dependency into the class
namespace VendorModuleDependency;
class MyDependency
{
public function doSomething()
{
// Do something
}
}
3. Use the injected dependency in your class
namespace VendorModuleModel;
class MyClass
{
protected $_dependency;
public function setDependency(VendorModuleDependencyMyDependency $dependency)
{
$this->_dependency = $dependency;
}
public function doSomething()
{
$this->_dependency->doSomething();
}
}
Interface Injection
1. Define the interface
namespace VendorModuleDependency;
interface MyDependencyInterface
{
public function doSomething();
}
2. Implement the interface
namespace VendorModuleDependency;
class MyDependency implements MyDependencyInterface
{
public function doSomething()
{
// Do something
}
}
3. Inject the dependency into the class
namespace VendorModuleModel;
class MyClass
{
protected $_dependency;
public function __construct(MyDependencyInterface $dependency)
{
$this->_dependency = $dependency;
}
public function doSomething()
{
$this->_dependency->doSomething();
}
}
Advanced DI Concepts You Should Know
1. Virtual Types
Virtual types in Magento 2 allow configuring concrete classes. Defining them is possible without modifying their original implementations. They act as placeholders for actual class configurations. Such enables flexibility and customization without altering core code.
Besides the types we mentioned earlier, understanding virtual types in Magento 2 would benefit you too. They allow configuring concrete classes, and it’s possible to define them without changing their initial implementations. These virtual types act as placeholders for the actual class configurations. It offers you the flexibility to customize without interfering with the core code.
Configuration in di.xml:
Specify virtual types in the di.xml configuration file. Provide the virtual type’s name and configuration.
Use the <virtualType>
tag to find a virtual type and define its parameters.
<virtualType name="VirtualTypeExample" type="ConcreteClass">
<arguments>
<argument name="argumentName" xsi:type="string">argumentValue</argument>
</arguments>
</virtualType>
Usage in Class Constructor
Add virtual types into the class constructors like other dependencies.
Magento 2 will instantiate and inject the configured actual class for the virtual type.
class MyClass {
public function __construct(VirtualTypeExample $virtualType) {
// Virtual type automatically resolves to the configured concrete class
}
}
2. Plugins (Interceptors)
Plugins (aka interceptors) help adjust the behavior of public class functions without changing the core code. They allow: before, after, and around method interception, which is flexible to extend and customize functions.
Configuration in di.xml:
- Configure modules in the di.xml file by defining the target and plugin classes
- Specify the type of interception: before, after, or around
- Define the method to intercept
<type name="TargetClass">
<plugin name="PluginName" type="PluginClass" sortOrder="10" disabled="false"/>
</type>
3. Plugin Class Implementation
Install the plugin class that using methods specific to the interception type. Utilize the method parameters to retrieve the original method arguments and return values.
class PluginClass {
public function beforeMethod($subject, $arg1, $arg2) {
// Modify arguments or perform actions before the original method is called
}
public function afterMethod($subject, $result) {
// Modify the result or perform actions after the original method is called
}
public function aroundMethod($subject, $proceed, $arg1, $arg2) {
// Execute custom logic before and after the original method
$result = $proceed($arg1, $arg2);
return $result;
}
}
Conclusion
Magento 2 Dependency Injection is a great way to reduce tight coupling between application codebases. Rather than hard-coding dependencies, you can inject the list of object that a class may need. Dependency Injection also enables you to manage future changes and other complexity in your codes.
Also, it is essential to follow the design patterns and coding standard while developing Magento 2. Although there’s more to learn about this topic, these were only the basics to get you started with Dependency Injection in Magento 2.