11

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?

14

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.

  • Is a preference always required for an interface parameter? Can it be seen as a fallback? Does it make sense to just define a concrete argument in the configuration without having a preference anywhere? Or is that not possible? – robsch May 27 at 12:05
6

It is important to understand the difference between definition of dependencies and configuration of dependencies.

Dependencies are not defined inside di.xml. Dependencies are defined inside the constructor of the respective class by specifying an interface, an abstract or a factory as the type of that specific dependecy, e.g. $routeConfig is a dependency of type \Magento\Framework\App\Route\ConfigInterface.

On the other hand, di.xml is the place to configure the dependencies by using <preference/> nodes and/or xpath:type/arguments/argument nodes (sometimes coupled with more advanced configuration nodes like <virtualType/> or <proxy/>). Configuring a dependency simply means mapping an object's constructor argument to an implementation/object/concrete.

You want dependencies to be configurable via di.xml so that you can swap them and use a different implementation for a certain interface or argument under certain conditions (keep reading the example to understand what certain conditions is supposed to mean).

For example, when developing your extension you would first create a new class (we call this new class an implementation). Your new class implements the \Magento\Framework\App\Route\ConfigInterface interface and it has inside its body a concrete functionality which honors the interface contract. Now starts the configuration part: in order to tell Magento to use your newly defined implementation you must configure this implementation as a dependecy for the object Magento\Backend\Model\Url. You do this configuration inside the di.xml files or your module. In this case you need to use the <preference/> node to map the interface to your new implementation. Other times you would use the more granular xpath:type/arguments/argument di.xml node to map only specific arguments (a.k.a. dependencies, a.k.a interfaces) of a concrete to specific implementations. Now, your implementation will only be active as a dependency for the object \Magento\Backend\Model\Url in certain conditions, e.g., in the code execution flow of the current application request an object of type Magento\Backend\Model\Url is being created and it needs an implementation for the constructor defined dependency called $routeConfig which is of type \Magento\Framework\App\Route\ConfigInterface.

It is pretty much like saying:

"Hey Mr. ObjectManager! Whenever an object instance of type Magento\Backend\Model\Url is requested, please have a look at its class constructor definition first and analyze the dependecies defined therein. I want you then to lookup inside the final, merged di.xml of the current HTTP request the configuration for each and every configured dependency that is defined in the Magento\Backend\Model\Url class constructor. You give me that configured dependency implementation."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.