di.xml
Where di.xml lives, how the files merge, which areas they can differ by, and how to read one before you change it.
app/code/Vendor/Module/etc/di.xml | app/code/Vendor/Module/etc/<area>/di.xmldi.xml is the file that tells Magento how to build objects: which implementation stands behind an interface, and what to pass to a constructor parameter that is not an object. It is read, merged and cached — it is not executed in any order you control.
Three locations
Magento reads, in this order: app/etc/di.xml (framework-level settings only), then every module's etc/di.xml (global), then every module's etc/<area>/di.xml. An area is one entry point into the application; the areas with their own di.xml are adminhtml, frontend, graphql, webapi_rest, webapi_soap and crontab. Put presentation wiring in the area file and everything else in the global one.
Merging
All files are merged by appending nodes. Nothing is namespaced per module, so two modules can configure the same class, and the one loaded later wins. That makes reading first a practical habit: search vendor/magento and app/code for the class name before you add a node for it, so you know what you are overriding.
Making it take effect
Run bin/magento cache:flush after any edit; the configuration cache holds the merged result. Run bin/magento setup:di:compile in production mode. To check what the merge produced, use bin/magento dev:di:info 'Vendor\Module\Service\ConfigFlag'.
Example
<!-- app/code/Vendor/Storefront/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Vendor\Storefront\Api\FlagInterface"
type="Vendor\Storefront\Service\ConfigFlag"/>
<type name="Vendor\Storefront\Service\ConfigFlag">
<arguments>
<argument name="configPath" xsi:type="string">vendor_storefront/general/enabled</argument>
<argument name="default" xsi:type="boolean">false</argument>
</arguments>
</type>
<type name="Vendor\Storefront\Service\LabelBuilder" shared="false"/>
</config>When to use it
- Passing a configuration path into a service as a string instead of hardcoding it.
- Finding out which module already configured a core class before you add your own node.
- Deciding whether a binding belongs in etc/di.xml or etc/adminhtml/di.xml.
More examples
An area file, and why it is a separate file
The same class gets a different argument in the admin than on the storefront, with no PHP change. Reach for an area file when the difference really is presentational. If the value should hold everywhere, including in the CLI and the REST API, it belongs in etc/di.xml instead, because the area files are never loaded there. Apply either with bin/magento cache:flush.
<!-- app/code/Vendor/Storefront/etc/adminhtml/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Vendor\Storefront\Service\LabelBuilder">
<arguments>
<argument name="maxLength" xsi:type="number">120</argument>
</arguments>
</type>
</config>Reading core before changing it
Real core configuration, worth reading because it shows the shape you will copy: an object argument naming a class, and an array argument whose items are named. An array argument you redeclare replaces the whole array unless you repeat the items you want to keep, so always read the original before writing your version.
<!-- app/etc/di.xml, shipped with Magento 2.4.9 -->
<type name="Magento\Framework\Logger\Handler\Base">
<arguments>
<argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
</arguments>
</type>
<type name="Magento\Framework\Logger\Monolog">
<arguments>
<argument name="name" xsi:type="string">main</argument>
<argument name="handlers" xsi:type="array">
<item name="system" xsi:type="object">Magento\Framework\Logger\Handler\System</item>
<item name="debug" xsi:type="object">Magento\Framework\Logger\Handler\Debug</item>
<item name="syslog" xsi:type="object">Magento\Framework\Logger\Handler\Syslog</item>
</argument>
</arguments>
</type>Shorthand and longhand in the XML itself
Shown together so both forms are recognisable; you would keep only one of each pair in a real file. The useful half is the default: object arguments and types are shared unless marked shared="false", which means one instance per request. Write shared="false" only when the object carries state that must not leak between callers.
<!-- app/code/Vendor/Storefront/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<!-- Shorthand: a self-closing node with no arguments. -->
<type name="Vendor\Storefront\Service\LabelBuilder" shared="false"/>
<!-- Longhand: the same node written out. Only one of the two forms
can appear in a file, because config.xsd requires a type name to
be unique per file:
<type name="Vendor\Storefront\Service\LabelBuilder" shared="false"></type> -->
<type name="Vendor\Storefront\Service\ConfigFlag">
<arguments>
<!-- An object argument is shared unless you say otherwise, so
the line below and this one request the same thing. Argument
names are a key within a type, so only one of them can be
present:
<argument name="escaper" xsi:type="object" shared="true">Magento\Framework\Escaper</argument> -->
<argument name="escaper" xsi:type="object">Magento\Framework\Escaper</argument>
</arguments>
</type>
</config>
Discussion