Injecting Values and Arrays
Argument nodes in di.xml pass scalars, constants and arrays of services into a constructor the object manager cannot guess.
<argument name="constructorParamName" xsi:type="string|boolean|number|null|object|array|const|init_parameter">value</argument>The object manager is the component that builds every object in Magento. It resolves a constructor parameter from its type declaration, which works for an interface and fails for a string, a batch size, or a list of three validators. An <argument> node in di.xml supplies those values.
How arguments are matched
Arguments sit inside an <arguments> block within a <type> or <virtualType> node. The name attribute is the constructor parameter name, spelled exactly: the object manager walks the real parameters and looks each one up by name, so a configured argument whose name matches nothing is skipped without complaint.
xsi:type declares the kind of value. The types are string, boolean, number, null, object, array, const and init_parameter. object takes a class, interface or virtual type name. const takes a fully qualified constant name and throws if the constant is not defined. number passes the literal text from the XML, which PHP then coerces to a declared int or float parameter.
Arrays and composites
An array argument holds <item> children, each with its own xsi:type, nested as deep as needed. An array of objects is how Magento builds a composite: one class takes many collaborators and loops over them. Magento\Framework\MessageQueue\Consumer\Config\CompositeValidator receives its four validators exactly this way in app/etc/di.xml.
Making the edit take effect
The merged configuration is cached. Run bin/magento cache:clean config in developer mode, or bin/magento setup:di:compile in production. Skipping that step is the usual reason an argument looks ignored.
Example
<!-- app/code/Vendor/Module/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Vendor\Module\Model\Export\Writer">
<arguments>
<argument name="directory" xsi:type="string">var/export/vendor</argument>
<argument name="batchSize" xsi:type="number">500</argument>
<argument name="includeDisabled" xsi:type="boolean">false</argument>
<argument name="scopeType" xsi:type="const">Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT</argument>
<argument name="validators" xsi:type="array">
<item name="sku" xsi:type="object">Vendor\Module\Model\Export\Validator\Sku</item>
<item name="price" xsi:type="object">Vendor\Module\Model\Export\Validator\Price</item>
</argument>
</arguments>
</type>
</config>When to use it
- Give an export class its output directory and batch size without a helper or a setter.
- Register two validators in one composite so another module can add a third later.
- Pass ScopeConfigInterface::SCOPE_TYPE_DEFAULT as a const argument instead of hardcoding 'default'.
- Hand a class the current application mode, which exists only as a bootstrap parameter.
More examples
The class that receives them, in both constructor styles
The di.xml above fills $directory, $batchSize, $includeDisabled, $scopeType and $validators; $scopeConfig needs no argument because its interface is enough to resolve it. The number 500 arrives as the text "500" and PHP coerces it to the declared int. Either constructor style receives the same arguments, so pick one and be consistent within a module.
<?php
// app/code/Vendor/Module/Model/Export/Writer.php
declare(strict_types=1);
namespace Vendor\Module\Model\Export;
use Magento\Framework\App\Config\ScopeConfigInterface;
class Writer
{
/**
* @param ValidatorInterface[] $validators
*/
public function __construct(
private readonly ScopeConfigInterface $scopeConfig,
private readonly string $directory,
private readonly int $batchSize = 100,
private readonly bool $includeDisabled = false,
private readonly string $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
private readonly array $validators = []
) {
}
}
// Constructor property promotion is shorthand. The longhand below is the
// same class: promotion only moves the property declaration and the
// assignment into the parameter list. Magento core is written in the long
// form, so you will read both.
//
// private ScopeConfigInterface $scopeConfig;
//
// private string $directory;
//
// public function __construct(
// ScopeConfigInterface $scopeConfig,
// string $directory
// ) {
// $this->scopeConfig = $scopeConfig;
// $this->directory = $directory;
// }An array of objects consumed as a composite
The composite holds no logic of its own; the array argument is the configuration. Because another module can add one more item to that array, this is how you build an extension point without an event or a plugin. The item name becomes the array key, which is why naming items after what they do pays off in the error message.
<?php
// app/code/Vendor/Module/Model/Export/Validator/Composite.php
declare(strict_types=1);
namespace Vendor\Module\Model\Export\Validator;
use Magento\Framework\Exception\LocalizedException;
class Composite implements ValidatorInterface
{
/**
* @param ValidatorInterface[] $validators
*/
public function __construct(
private readonly array $validators = []
) {
}
/**
* @throws LocalizedException
*/
public function validate(array $row): void
{
foreach ($this->validators as $name => $validator) {
try {
$validator->validate($row);
} catch (LocalizedException $e) {
throw new LocalizedException(
__('Row rejected by %1: %2', $name, $e->getMessage())
);
}
}
}
}A value that only exists at bootstrap: init_parameter
init_parameter resolves the named constant and then looks that value up among the parameters the application was bootstrapped with, falling back to the parameter default if it is absent. Core uses this exact argument for Magento\Framework\App\State and Magento\Framework\View\Asset\Source in app/etc/di.xml, which is how a class learns whether it is running in developer or production mode. Apply the change with bin/magento cache:clean config.
<?xml version="1.0"?>
<!-- app/code/Vendor/Module/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Vendor\Module\Model\Export\Writer">
<arguments>
<argument name="appMode" xsi:type="init_parameter">Magento\Framework\App\State::PARAM_MODE</argument>
</arguments>
</type>
</config>
Discussion