Magento 2 Dependency Injection in Constructor and di.xml Explained

dependency-injectiondimagento-2.1magento2

I don't understand why, in some classes, their dependency injections are declared twice – once in the di.xml and in the concrete class's constructor.

For instance in Magento\Backend\Model\Url, its di.xml has this set of types for DI defined:

<type name="Magento\Backend\Model\Url">
    <arguments>
        <argument name="scopeResolver" xsi:type="object">
Magento\Backend\Model\Url\ScopeResolver</argument>
        <argument name="authSession" xsi:type="object">
Magento\Backend\Model\Auth\Session\Proxy</argument>
        <argument name="formKey" xsi:type="object">
Magento\Framework\Data\Form\FormKey\Proxy</argument>
        <argument name="scopeType" xsi:type="const">
Magento\Store\Model\ScopeInterface::SCOPE_STORE </argument>
        <argument name="backendHelper" xsi:type="object">
Magento\Backend\Helper\Data\Proxy</argument>
    </arguments>
</type>

But at the same time, in its concrete class, those classes defined in di.xml required for injection is re-declared again in the constructor:

<?php
    public function __construct(
        \Magento\Framework\App\Route\ConfigInterface $routeConfig,
        \Magento\Framework\App\RequestInterface $request,
        \Magento\Framework\Url\SecurityInfoInterface $urlSecurityInfo,
        \Magento\Framework\Url\ScopeResolverInterface $scopeResolver,
        \Magento\Framework\Session\Generic $session,
        \Magento\Framework\Session\SidResolverInterface $sidResolver,
        \Magento\Framework\Url\RouteParamsResolverFactory $routeParamsResolverFactory,
        \Magento\Framework\Url\QueryParamsResolverInterface $queryParamsResolver,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        $scopeType,
        \Magento\Backend\Helper\Data $backendHelper,
        \Magento\Backend\Model\Menu\Config $menuConfig,
        \Magento\Framework\App\CacheInterface $cache,
        \Magento\Backend\Model\Auth\Session $authSession,
        \Magento\Framework\Encryption\EncryptorInterface $encryptor,
        \Magento\Store\Model\StoreFactory $storeFactory,
        \Magento\Framework\Data\Form\FormKey $formKey,
        array $data = []
) {
    //...
}
?>

If we look at its constructor above, \Magento\Framework\App\Route\ConfigInterface $routeConfig, for example, is not defined in di.xml. It is only defined in the constructor and Magento will still inject the routeConfig into the class for use, wouldn't it? Same for \Magento\Framework\Encryption\EncryptorInterface $encryptor and a few others.

Then, why is there a need to define the other injections in both di.xml and in the constructor when having those declarations in the constructor is sufficient for Magento to inject those dependencies into the class for use?

Best Answer

As stated in the documentation, in Magento 2, the di.xml can be used to do the following:

You can configure the class constructor arguments in your di.xml in the argument node. The object manager injects these arguments into the class during creation. The name of the argument configured in the XML file must correspond to the name of the parameter in the constructor in the configured class.

In your case it's slightly complex I'm going to explain each argument one by one:

  • \Magento\Framework\App\Route\ConfigInterface $routeConfig : this is an interface so it's not usable directly. The preference for this class is defined in app/etc/di.xml and it is the Magento\Framework\App\Route\Config class
  • \Magento\Framework\App\RequestInterface $request : same goes for this class, the preference is Magento\Framework\App\Request\Http
  • \Magento\Framework\Url\SecurityInfoInterface $urlSecurityInfo : same case here again with Magento\Framework\Url\SecurityInfo\Proxy as the preference
  • \Magento\Framework\Url\ScopeResolverInterface $scopeResolver : here we start with the interesting bit. In app/etc/di.xml a preference is defined for this interface and it is the Magento\Framework\Url\ScopeResolver class. However, for the Magento\Backend\Model\Url Magento 2 needs to use another class and thus it defines which class in the di.xml you posted so Magento\Backend\Model\Url\ScopeResolver will be used.
  • \Magento\Framework\Session\Generic $session this is a normal class and thus can be used as it.
  • \Magento\Framework\Session\SidResolverInterface $sidResolver : back to an interface, the preference is still defined in app/etc/di.xml and it is Magento\Framework\Session\SidResolver\Proxy
  • \Magento\Framework\Url\RouteParamsResolverFactory $routeParamsResolverFactory : this is a factory class so it can be used as it.
  • \Magento\Framework\Url\QueryParamsResolverInterface $queryParamsResolver : back to our app/etc/di.xml and the preference is Magento\Framework\Url\QueryParamsResolver
  • \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig : another case here where **a preference is defined in app/etc/di.xml and it is Magento\Framework\App\Config .
  • $scopeType : here we only have a variable without any class in front of it. Your module di.xml specifies that Magento\Store\Model\ScopeInterface::SCOPE_STORE should be used as the value of this variable.**
  • \Magento\Backend\Helper\Data $backendHelper : here we could have use that class as it. However here a proxy is used because this class is not necessarily being used (see this post for details about proxy classes: Magento 2: practical explanation of what is a proxy class? )
  • \Magento\Backend\Model\Menu\Config $menuConfig : we can use this class as it.
  • \Magento\Framework\App\CacheInterface $cache : another preference defined in app/etc/di.xml for this interface which is Magento\Framework\App\Cache\Proxy
  • \Magento\Backend\Model\Auth\Session $authSession : same again here we could have used the class as it but we use a proxy class instead for lazy loading.
  • \Magento\Framework\Encryption\EncryptorInterface $encryptor : jumping to the app/etc/di.xml again and we find Magento\Framework\Encryption\Encryptor as a preference
  • \Magento\Store\Model\StoreFactory $storeFactory : a factory so we can use it as it.
  • \Magento\Framework\Data\Form\FormKey $formKey : here we use a the Magento\Framework\Data\Form\FormKey\Proxy proxy class again for lazy loading.
  • array $data = [] : this one always comes last and is automatically defaulted to an empty array you can find more information here: Magento 2: what is the $data array constructor parameter?

To summarize

Globally, classes constructors parameters are interfaces or non instantiable classes. Thus di.xml lets you tailor the dependencies you want to use for each class constructor. It's also valid for instantiable classes. For example a class constructor that takes a product class as a constructor argument. It can be tailored in the configurable product module so it takes a configurable product class instead as an argument.