Preferences
A preference binds an interface to the class Magento builds for it, site-wide, with the last module loaded winning.
<preference for="{InterfaceOrClassName}" type="{ImplementationClassName}"/>A preference answers one question: when a constructor asks for an interface, which class arrives. Without one, Magento has nothing to build and the request fails because an interface cannot be instantiated.
Declaring one
In di.xml: <preference for="Vendor\Module\Api\FlagInterface" type="Vendor\Module\Service\ConfigFlag"/>. Core does the same thing hundreds of times, for example Magento\Framework\View\Template\Html\MinifierInterface bound to Magento\Framework\View\Template\Html\Minifier in app/etc/di.xml. Your own interfaces always need one.
Shorthand and longhand
Type-hinting the interface and letting the preference resolve it is the short form. The long form names the class directly in a type argument: <argument name="flag" xsi:type="object">Vendor\Module\Service\ConfigFlag</argument>. Both end with the same object in the same constructor. The difference is reach: a preference changes every consumer, an argument changes exactly one.
The risk
A preference is global to its area, and the merge has no concept of ownership. Two modules preferring the same interface do not error; the one loaded later simply wins, and the other module's behaviour disappears. Overriding a core interface also means your class must keep up with every change core makes to the original. Prefer the narrowest tool that works.
Making it take effect
Run bin/magento cache:flush, then bin/magento setup:di:compile in production mode. Confirm the winner with bin/magento dev:di:info.
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"/>
</config>When to use it
- Making your own interface injectable, so other modules can depend on the contract instead of the class.
- Replacing a core implementation whose behaviour you need to change wholesale.
- Working out which of two installed modules is supplying the implementation you are actually getting.
More examples
The interface the preference points at
Publish the interface, bind it with a preference, and have every consumer type-hint FlagInterface rather than ConfigFlag. You can then change the implementation, or let a merchant's own module replace it, without editing a single consumer. This is the normal shape for anything in your module that other code will call.
<?php
declare(strict_types=1);
// app/code/Vendor/Storefront/Api/FlagInterface.php
namespace Vendor\Storefront\Api;
interface FlagInterface
{
/**
* @param int|null $storeId
* @return bool
*/
public function isEnabled(?int $storeId = null): bool;
}Overriding a core preference
Your class should extend Magento\Framework\View\Template\Html\Minifier and override only the method you care about, so you inherit the constructor and keep working when core adds a dependency. Every caller in every module now gets your class, which is the point and also the risk. After the edit run bin/magento cache:flush, and bin/magento setup:di:compile in production mode.
<!-- 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">
<!-- Core binds this interface to
Magento\Framework\View\Template\Html\Minifier in app/etc/di.xml. -->
<preference for="Magento\Framework\View\Template\Html\MinifierInterface"
type="Vendor\Storefront\View\Template\Html\Minifier"/>
</config>A preference that only applies in one area
Useful when the admin genuinely needs different behaviour, for example a preview that ignores the storefront switch. It is also a common source of confusion: the storefront, the CLI and the REST API keep the global binding, so the same code path returns different answers depending on where it ran. Check with bin/magento dev:di:info before assuming which class you got.
<!-- 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">
<preference for="Vendor\Storefront\Api\FlagInterface"
type="Vendor\Storefront\Service\AlwaysEnabledFlag"/>
</config>
Discussion