Preferences

A preference binds an interface to the class Magento builds for it, site-wide, with the last module loaded winning.

Syntax<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

Example · xml
<!-- 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.

Example · php
<?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.

Example · xml
<!-- 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.

Example · xml
<!-- 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

  • Be the first to comment on this lesson.