Skip to content
intermediate Phase 18 · Events, Plugins & DI

Magento 2 Preferences

Interface to class mapping, when preferences are used, and conflicts between preferences.

45m
0 problems
Topic Progress 0%

What Are Preferences?

Preferences in Magento 2 map an interface to a concrete class implementation. When a class type-hints an interface, the ObjectManager automatically injects the preferred implementation.

Preference configuration:

<!-- app/code/Vendor/Module/etc/di.xml -->
<config>
    <preference for="Magento\Catalog\Api\ProductRepositoryInterface"
                type="Magento\Catalog\Model\ProductRepository"/>
</config>

This means any constructor that type-hints ProductRepositoryInterface receives ProductRepository.

// This class receives ProductRepository automatically
public function __construct(
    \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
) {
    $this->productRepository = $productRepository;
}

Preference vs direct class usage:

// Bad - tight coupling to specific implementation
public function __construct(
    \Magento\Catalog\Model\ProductRepository $productRepository
) {
    $this->productRepository = $productRepository;
}

// Good - loose coupling via interface
public function __construct(
    \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
) {
    $this->productRepository = $productRepository;
}

Preferences are defined in the global di.xml (not area-specific) because they apply to all areas. Once compiled, the preference mapping is stored in generated/metadata/global.php.

Creating Custom Preferences

You can create preferences for any interface to provide alternative implementations.

Custom repository implementation:

<?php
namespace Vendor\Module\Model;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Api\Data\ProductInterface;

class CustomProductRepository implements ProductRepositoryInterface
{
    private $resource;
    private $productFactory;
    
    public function __construct(
        \Magento\Catalog\Model\ResourceModel\Product $resource,
        \Magento\Catalog\Model\ProductFactory $productFactory
    ) {
        $this->resource = $resource;
        $this->productFactory = $productFactory;
    }
    
    public function getById($productId, $editMode = false, $storeId = null)
    {
        $product = $this->productFactory->create();
        $this->resource->load($product, $productId);
        return $product;
    }
    
    public function save(ProductInterface $product)
    {
        // Custom save logic
        $this->resource->save($product);
        return $product;
    }
}

Register the preference:

<config>
    <preference for="Magento\Catalog\Api\ProductRepositoryInterface"
                type="Vendor\Module\Model\CustomProductRepository"/>
</config>

Custom interface and implementation:

// Api/NotificationInterface.php
<?php
namespace Vendor\Module\Api;

interface NotificationInterface
{
    public function send(string $message): bool;
    public function getHistory(): array;
}
<!-- di.xml -->
<config>
    <preference for="Vendor\Module\Api\NotificationInterface"
                type="Vendor\Module\Model\Notification"/>
</config>

Preference Conflicts

When multiple modules define preferences for the same interface, only one can win. Magento resolves conflicts based on module sequence order.

Conflict scenario:

<!-- Module A -->
<preference for="Some\Interface" type="ModuleA\Implementation"/>

<!-- Module B -->
<preference for="Some\Interface" type="ModuleB\Implementation"/>

Resolution:
Magento uses the module sequence (dependency order) defined in module.xml. The module that appears later in the sequence wins.

<!-- Module B depends on Module A -->
<module name="Module_B">
    <sequence>
        <module name="Module_A"/>
    </sequence>
</module>

Check preference conflicts:

php bin/magento setup:di:compile
# Watch for warning messages about conflicting preferences

Avoiding conflicts:

  • Use virtual types instead of preferences for variations
  • Check if a preference already exists before defining one
  • Use module sequence to control preference priority
  • Prefer plugins over preferences for behavior modification

Virtual type alternative:

<!-- Instead of overriding the preference, create a virtual type -->
<config>
    <virtualType name="CustomNotification" type="Vendor\Module\Model\Notification">
        <arguments>
            <argument name="apiKey" xsi:type="string">custom-key</argument>
        </arguments>
    </virtualType>
    
    <type name="Vendor\Module\Service\Notifier">
        <arguments>
            <argument name="notification" xsi:type="object">CustomNotification</argument>
        </arguments>
    </type>
</config>

Virtual types create named configurations without changing the preference.

Preferences vs Plugins vs Virtual Types

Each extension mechanism has its use case. Choosing the right one is important for maintainability.

When to use preferences:

  • Replace entire implementation of an interface
  • No existing implementation works for your needs
  • You control the interface and want a specific implementation

When to use plugins:

  • Add behavior before/after existing methods
  • Modify return values of methods
  • Don't need to change the implementation class

When to use virtual types:

  • Need different configurations of the same class
  • Don't want to change the global preference
  • Create specialized instances for specific use cases

Comparison table:

| Mechanism      | Scope    | Use Case                    | Reversible |
|---------------|----------|-----------------------------|------------|
| Preference    | Global   | Replace implementation      | No*        |
| Plugin        | Method   | Modify method behavior      | Yes        |
| Virtual Type  | Instance | Create configured variant   | Yes        |

Example: Using all three:

<config>
    <!-- Preference: Replace interface implementation -->
    <preference for="Vendor\Api\LoggerInterface"
                type="Vendor\Model\CustomLogger"/>
    
    <!-- Virtual Type: Different config for same class -->
    <virtualType name="DebugLogger" type="Vendor\Model\CustomLogger">
        <arguments>
            <argument name="logLevel" xsi:type="string">debug</argument>
        </arguments>
    </virtualType>
    
    <!-- Plugin: Add behavior to methods -->
    <type name="Vendor\Model\CustomLogger">
        <plugin name="vendor_log_timing" type="Vendor\Plugin\LogTiming"/>
    </type>
</config>

Best practice: Start with plugins, use virtual types for configuration variations, and only use preferences when you must completely replace an implementation.

Quiz

1. What does a Magento preference do?

Question 1 options

2. How are preference conflicts resolved between modules?

Question 2 options

3. What is a virtual type in Magento DI?

Question 3 options

Flashcards

Question

Where are preferences defined?

Answer

In etc/di.xml at the module root level (global scope)

Question

What is the benefit of using interfaces with preferences?

Answer

Loose coupling - the implementation can be swapped without changing dependent code

Question

What is a better alternative to preferences for method behavior changes?

Answer

Plugins (interceptors)

Question

When should you use virtual types over preferences?

Answer

When you need different configurations of the same class in different contexts

Revision Notes

Key Takeaways

  • 1. Preferences map interfaces to concrete class implementations
  • 2. Defined in global di.xml (applies to all areas)
  • 3. Conflicts resolved by module sequence order
  • 4. Plugins are preferred for modifying method behavior
  • 5. Virtual types create configured variants without changing preferences
  • 6. Use interfaces with constructor injection for loose coupling

Interview Tips

  • Explain what preferences are and when to use them
  • Describe how preference conflicts are resolved
  • Compare preferences, plugins, and virtual types
  • Discuss why interfaces are important in Magento DI
  • Know when to use virtual types instead of preferences

Cheat Sheet

Preferences Cheat Sheet

Config: etc/di.xml (global scope)

<preference for="Interface\Name" type="Implementation\Class"/>

Conflict Resolution: Module sequence order wins

Alternatives:

  • Plugins → Modify method behavior
  • Virtual Types → Configured variants

Best Practice:

  1. Plugins (first choice for behavior changes)
  2. Virtual Types (for configuration variants)
  3. Preferences (only for full replacement)