Dependency Injection In Magento 2
Dependency injection is a special design pattern for software developed to implement control inversion and provide a program with the ability to follow the principle of dependency inversion. Spring, Glassfish HK2, Guice, Microsoft Managed Extensibility Framework and other application frameworks support dependency injection.
An injection is the passing of a service to a client (a dependency to a dependent object). The service is made part of the client’s state. The pattern is based on the next fundamental requirement: a service is passed to a client, instead of allowing a client to find the service. It separates the creation of client’s dependencies from its own behavior. As a result, program designs are loosely coupled and able to follow the single responsibility and dependency inversion principles. The opposite approach is implemented in the service locator pattern, which is designed to allow clients to get the information about the system used to find dependencies.
Everything about Magento 2 on Firebear
Dependency injection consists of four major elements:
- a service object implementation;
- a client object depending on a service;
- an interface, which connects client with a service;
- an injector object, which injects service into a client ( it can be provider, assembler, container, spring of factory).
Dependency Injection Advantages
- Dependency injection can be applied to legacy code as a refactoring, because it requires no change in code behavior.
- It allows a clients to remove all knowledge of a concrete implementation that they need to use.
- The pattern can be used to externalize configuration details of a system into configuration files. This feature provides the ability to reconfigure the system without recompilation.
- It also reduces the boilerplate code in different application objects.
- Dependency injection allows independent development.
Dependency Injection Disadvantages
- Dependency injection can make code more difficult to read as in case with legacy code.
- It also requires some extra lines of code for the same behavior.
- Dependency injection diminishes encapsulation.
Overview of dependency injection in Magento 2
Dependency injection became an alternative to the Magento 1.x Mage class. In simple terms, when Module 1 needs an access to some functions of Module 2, Module 1 depends on Module 2, so it consumes the service provided by Module 2. In this situation, Module 1 is called the consumer and Module 2 – the dependent.
A coding principle that is used to reduce code dependencies is a dependency inversion. According to dependency inversion, both high and low-level modules should depend on abstractions. Details also should depend on abstractions, but at the same time, abstractions should not depend upon details.
Links
Dependency injection preview
Magento 2 Сonstructor injection
Constructor injection is used for required and optional service dependencies of an object. You should use a proxy patterns for expensive optional dependencies; the good news is they are auto-generated, so no coding is necessary.
A sample proxy (declared in di.xml):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?php <type name="Magento\Backend\Model\Config\Structure\Element\Iterator\Field" shared="false"> <arguments> <argument name="groupFlyweight" xsi:type="object">Magento\Backend\Model\Config\Structure\Element\Group\Proxy</argument> </arguments> </type> <?php class Foo { protected $_bar; public function __construct(Bar $bar) { $this->_bar = $bar; } public function execute() { //some code $this->_bar->execute(); //some code } } $bar = new Bar(); $foo = new Foo($bar); $foo->execute(); |
Method injection
Method injection is used for API objects that your object acts on. The following example illustrates how to use this type of injection to declare $menu and $itemFactory as service dependencies, and use $command as the API parameter that your object acts on
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php namespace Magento\Backend\Model\Menu; class Builder { /** * @param \Magento\Backend\Model\Menu\Item\Factory $menuItemFactory * @param \Magento\Backend\Model\Menu $menu */ public function __construct( Magento\Backend\Model\Menu\Item\Factory $menuItemFactory, // Service dependency Magento\Backend\Model\Menu $menu // Service dependency ) { $this->_itemFactory = $menuItemFactory; $this->_menu = $menu; } public function processCommand(\Magento\Backend\Model\Menu\Builder\CommandAbstract $command) // API param { if (!isset($this->_commands[$command->getId()])) { $this->_commands[$command->getId()] = $command; } else { $this->_commands[$command->getId()]->chain($command); } return $this; } } |
Configuration overview
The object manager requires the below configurations:
- Class definitions. They are used to retrieve types and numbers of class dependencies;
- Instance configurations. They are used to retrieve how all the objects are instantiated and what is their lifestyle;
- Abstraction-implementation mappings. They are used to define what implementation should be used upon the request to an interface
app/etc/di/*.xml, <your module dir>/etc/<areaname>/di.xml, and<your module dir>/etc/di.xml are files required to define the object manager interface preferences, depending on the level. app/code/core/Magento/Backend/etc/adminhtml/di.xml is used to set the interface preferences for the Magento Admin:
1 2 3 |
<config> <preference for="Magento_Core_Model_UrlInterface" type="Magento_Backend_Model_Url" /> </config> |
1 2 3 4 5 |
<type name="Magento\Filesystem" shared="false"> <arguments> <argument name="adapter" xsi:type="object" shared="false">Magento\Filesystem\Adapter\Local</argument> </arguments> </type> |
To specify whether or not the object is shareable in its di.xml use the following:
All dependency injection configurations can be validated by
The levels of object manager configurations :
- (app/etc/di/*.xml) – global across Magento
- (<your module directory>/etc/di.xml) – the whole module
- (<your module directory>/etc/<areaname>/di.xml) – configuration for the specific area of a module
Keep in mind, that area configurations override global configurations.
Class definitions
To define information about class dependencies, Magento relies on class constructor signatures. It reads constructors using reflection, and it is strongly recommend to use the Magento definition compiler tool to pre-compile class definitions, if you are going to get better performance. The parameters specified for class types are inherited by their descendant classes.
Type configurations
Type is the scope of the dependency, including all of Magento, module and module area.
Specify types
This is the example of dependency injection by type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <type name="Magento\Core\Model\Session" /> <!-- Default instance of Magento\Core\Model\Session type. Exists by default, can be omited. --> <virtualType name="moduleConfig" type="Magento\Core\Model\Config"> <!-- Instance with global name "config" of Magento\Core\Model\Config type --> <arguments> <argument name="type" xsi:type="string">system</argument> </arguments> </virtualType> <type name="Magento\Core\Model\App"> <arguments> <argument name="config" xsi:type="object">moduleConfig</argument> </arguments> </type> </config> |
Magento\Core\Model\Session (set explicitly or taken from the name)The example declares the following types:
- config – this virtual type extends Magento\Core\Model\Config
- moduleConfig – extends Magento\Core\Model\Config
- Magento\Core\Model\App – the instances of this type retrieve moduleConfig as a dependency
Arguments
Arguments must be injected into a class instance at a time of its creation. Parameter names must match the configured class constructor parameters. The Object Manager defines:
- Parameter – declared in the constructor signature variable.
- Argument – value passed to the constructor after the creation of class instance.
In the following example the sample argument creates instances of Magento\Core\Model\Session and the argument $sessionName is set to a value of adminhtml:
|
1 2 3 4 5 6 7 |
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <type name="Magento\Core\Model\Session"> <arguments> <argument name="sessionName" xsi:type="string">adminhtml</argument> </arguments> </type> </config> |
Argument definitions
Node format | Description | Possible values |
Object with default lifestyle
<argument xsi:type=”object”> Object with specified lifestyle <argument xsi:type=”object” |
Creates an instance of Type_Name type. Any class, interface, or virtual type name can be passed as Type_Name.
shared determines the created instance lifestyle. |
n/a |
Node format | Description | Possible values |
Regular string
<argument xsi:type=”string”> Translated string <argument xsi:type=”string” |
someValue is passed as a string. | All values are passed as a strings. |
Node format | Description | Possible values |
<argument xsi:type=”boolean”> {boolValue}</argument> |
boolValue value is turned (converted) into bool | Look into the following tabl. |
Input type | Input data | Interpreted Boolean type |
Boolean | true | true |
Boolean | false | false |
String | “true” | true |
String | “false” | false |
String | “1” | true |
String | “0” | false |
Integer | 1 | true |
Integer | 0 | false |
Keep in mind, that all string literals are case-sensitive.
Node format | Description | Possible values |
<argument xsi:type=”number”> {numericValue}</argument> |
numericValue as-is | float, integer, or a |
Node format | Description | Possible values |
<argument xsi:type=”init_parameter”> {Constant::NAME}</argument> |
Global argument of an application represented by Constant::NAME is looked up and passed as an argument. | Constant the global argumentcontaining name |
Node format | Description | Possible values |
<argument xsi:type=”const”> {Constant::NAME}</argument> |
Constant::NAME passed as an argument. | All constant names are possible. |
Node format | Description | Possible values |
<argument xsi:type=”null”/> | Pass null as an argument. | n/a |
Node format | Description | Possible values |
<argument xsi:type=”array”> <item key=”someItem” xsi:type=”string”>someVal</item> </argument> |
Array with elements (the infinite number of items) corresponding to the items passed as an argument. The items can have any type as arguments, including both an object type and array itself. | n/a |
The example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <type name="Magento\Example\Type"> <arguments> <!-- Pass simple string --> <argument name="stringParam" xsi:type="string">someStringValue</argument> <!-- Pass instance of Magento\Some\Type --> <argument name="instanceParam" xsi:type="object">Magento\Some\Type</argument> <!-- Pass true --> <argument name="boolParam" xsi:type="boolean">1</argument> <!-- Pass 1 --> <argument name="intParam" xsi:type="number">1</argument> <!-- Pass application init argument, named by constant value --> <argument name="globalInitParam" xsi:type="init_parameter">Magento\Some\Class::SOME_CONSTANT</argument> <!-- Pass constant value --> <argument name="constantParam" xsi:type="const">Magento\Some\Class::SOME_CONSTANT</argument> <!-- Pass null value --> <argument name="optionalParam" xsi:type="null"/> <!-- Pass array --> <argument name="arrayParam" xsi:type="array"> <!-- First element is value of constant --> <item name="firstElem" xsi:type="const">Magento\Some\Class::SOME_CONSTANT</item> <!-- Second element is null --> <item name="secondElem" xsi:type="null"/> <!-- Third element is a subarray --> <item name="thirdElem" xsi:type="array"> <!-- Subarray contains scalar value --> <item name="scalarValue" xsi:type="string">ScalarValue</item> <!-- and application init argument --> <item name="globalArgument " xsi:type="init_parameter">Magento\Some\Class::SOME_CONSTANT</item> </item> </argument> </arguments> </type> </config> |
Arguments with the same name will be completely replaced after configuration is merged. For different argument types and the same names, arguments are overridden.
Parameter configuration inheritance
Configured for a class type parameters are configured for all of their descendants automatically. It their turn, descendants are able to override parameters configured for the supertype:
|
|
Lifestyle management
Every object has a lifestyle, which determines in what scope instances should be reused, and when is the correct moment to release them.
The object manager works with the following types of objects:
- singleton — one class instance is created at the first request. This instance is subsequently reused. It is released when the container is disposed.
- transient — a new class instance is created every time, when the class is requested.
To configure the preceding lifestyles you can us:
- argument (it only defines the lifestyle of an argument);
- type (this convenience configuration defines lifestyles for all instances of the certain type).
Injectables and non-injectables
Some objects be instantiated by the object manager. They are called Injectable. Object that can’t be instantiated are non-injectable. They have a transient lifestyle and requires external to be created. The better part of models are non-injectable.
- Non-injectables can’t request other objects in a constructor – injectables can do this.
- If an injectable object produces non-injectables, then it must ask for the factory in the constructor.
- If an injectable object performs some actions on non-injectable, then it must receive the non-injectable as a method argument
You always have the ability to both pass non-injectables in as method parameters or create them in services with object factories.
Keep in mind, that the push of injectables to non-injectables violates the
Factories
The only purpose of factories is the creation of an for one non-injectable class or interface. They are able to depend on the object manager and are used to isolate OM from a business code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php class Magento\Core\Model\Config\BaseFactory { protected $_objectManager; public function __construct(Magento\Framework\ObjectManager $objectManager) { $this->_objectManager = $objectManager; } public function create($sourceData = null) { return $this->_objectManager->create('Magento\Core\Model\Config\Base', array('sourceData' => $sourceData)); } } |
Definition compiler tool
Сlass definitions are read with reflection by default. PHP reflection is slow, so you should use definition compiler tool. It is able to:
- generate all required factories; proxies declared in di.xml ; and interceptors for all classes with plug-ins in di.xml;
- compile definitions for all libraries and modules; class inheritance implementation relations; and plug-in definitions.
The compiler tool creates the following files and directories:
- <your Magento install dir>/var/generation directory with all generated classes and modules.
- <your Magento install dir>/var/di directory with definitions.php for compiled definitions; relations.php for class inheritance implementation relations; and plugins.php for declared public methods in plug-in definitions.
It is necessary to understand that the tool doesn’t analyze auto-generated factory classes in files from <your Magento install dir>lib/internal/Magento directory.
Create factory classes at the library level manually. Don’t use auto-generation.
Use di.xml
only to declare proxy classes.
Use PHP file’s __construct of a class located under<your Magento install dir>app/code to declare factory classes.
Naming rules for an auto-generated proxy class: Some\Model\Name\Proxy; for an auto-generated factory class: Some\Model\NameFactory
Running the definition compiler tool
To run the definition compiler tool , you should first of all be logged in as the web server user. Then, you should change to the [your Magento install dir]/dev/tools/Magento/Tools/Di directory.
This is the command syntax:
1 |
php compiler.php [--serializer <word>] [--verbose|-v] [--extra-classes-file <string>] [--generation <string>] [--di <string>] [--help] |
If you need to wrap strings, you should use double quotes (“).
Option | Description |
–serializer <word> | Uses serialize (the default one) or binary. |
–verbose | v | With it you will get the verbose output, without it – errors only. |
–extra-classes-file <string> | By including it, you will specify factories that are not included into the code base. |
–generation <string> | It shows the absolute filesystem path necessary to generate service classes: <magento_root>/var/di (the default one). |
–help | This option displays command help |
Specifying extra classes
–extra-classes-file is a special parameter developed to generate proxies and factories which are not declared in dependency injection or the code base of Magento. Here is the example:
1 2 3 4 5 6 |
<?php return array( 'Magento\Core\Model\SomeFactory', 'Magento\Core\Model\Some\Proxy' ); |
Sample commands
1. Running the definition tool in verbose mode
php compiler.php -v
As a result you should get the following output:
Generated classes:
Magento\AdminNotification\Model\FeedFactory
Magento\AdminNotification\Model\InboxFactory
Magento\Authorization\Model\Acl\Role\GroupFactory
Magento\Authorization\Model\Acl\Role\UserFactory
Magento\Authorization\Model\Resource\Role\CollectionFactory
Magento\Authorization\Model\Resource\Rules\CollectionFactory
Magento\Authorization\Model\RoleFactory
… (more)
2. Specifying an alternate path to generated files
php compiler.php –generation “/var/www/magento2/mydir”
To run the definition compiler tool you must have a write access permission to the directory you specify.
And don’t forget to check the