Skip to content
intermediate Phase 27 · DI Fundamentals

Preferences Deep Dive

Advanced guide to Magento 2 preferences: global vs area preferences, preference conflicts, when to use preferences vs plugins, and best practices

45m
0 problems
Topic Progress 0%

Preference Configuration

Basic Preference

Bind an interface to an implementation:

<preference for="Amazon\Prep\Api\WarrantyRepositoryInterface"
            type="Amazon\Prep\Model\WarrantyRepository"/>

Now anywhere WarrantyRepositoryInterface is type-hinted, DI injects WarrantyRepository.

Global Preference

<!-- etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Amazon\Prep\Api\WarrantyRepositoryInterface"
                type="Amazon\Prep\Model\WarrantyRepository"/>
</config>

Area-Specific Preference

<!-- etc/frontend/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Amazon\Prep\Api\WarrantyRepositoryInterface"
                type="Amazon\Prep\Model\Frontend\WarrantyRepository"/>
</config>

Area-specific preferences override global ones.

How Preferences Work

1. ObjectManager reads di.xml preferences
2. When interface is type-hinted, checks preferences
3. Returns the type (implementation class)
4. Creates instance with DI resolution

Multiple Preferences

If two modules define preferences for the same interface:

<!-- Module A -->
<preference for="Interface" type="ImplementationA"/>

<!-- Module B -->
<preference for="Interface" type="ImplementationB"/>

Magento uses the last-loaded module's preference. This can cause conflicts.

Preference Conflicts

Common Conflict Scenarios

Two Modules Override Same Interface

<!-- Magento_Catalog -->
<preference for="ProductInterface" type="Magento\Catalog\Model\Product"/>

<!-- Amazon_Prep -->
<preference for="ProductInterface" type="Amazon\Prep\Model\Product"/>

The module that loads last wins. This is unpredictable.

Resolving Conflicts

Option 1: Use Plugins Instead

<!-- Don't replace, extend -->
<type name="Magento\Catalog\Model\Product">
    <plugin name="my_product_plugin" type="Amazon\Prep\Plugin\ProductPlugin"/>
</type>

Option 2: Virtual Types

<!-- Create a virtual type instead of preference -->
<virtualType name="AmazonWarrantyRepository" type="Amazon\Prep\Model\WarrantyRepository">
    <arguments>
        <argument name="customArg" xsi:type="string">value</argument>
    </arguments>
</virtualType>

<!-- Use virtual type in other configs -->
<type name="Amazon\Prep\Service\WarrantyService">
    <arguments>
        <argument name="repository" xsi:type="object">AmazonWarrantyRepository</argument>
    </arguments>
</type>

Option 3: Use Module Sequence

<!-- module.xml -->
<module name="Amazon_Prep" setup_version="1.0.0">
    <sequence>
        <module name="Magento_Catalog"/>
    </sequence>
</module>

Ensure your module loads after the one you're extending.

Debug Preferences

# Check compiled preferences
bin/magento setup:di:compile

# Check generated code
cat var/di/development.log | grep -A5 "WarrantyRepositoryInterface"

# List all preferences
grep -r "preference for" vendor/magento/ etc/ app/code/

Preferences vs Plugins

When to Use Preferences

Use preferences when:

  • You need to replace an implementation entirely
  • The original class has no extension points
  • You're implementing a new service contract
  • You need to change the class hierarchy
<!-- Replacing a class entirely -->
<preference for="Amazon\Prep\Api\CacheInterface"
            type="Amazon\Prep\Model\RedisCache"/>

When to Use Plugins

Use plugins when:

  • You want to extend existing behavior
  • You want to intercept method calls
  • You need before/after/around hooks
  • You want to preserve original functionality
<!-- Extending behavior -->
<type name="Magento\Catalog\Model\Product">
    <plugin name="warranty_check" type="Amazon\Prep\Plugin\ProductPlugin"/>
</type>

Comparison

Aspect Preference Plugin
Purpose Replace class Extend behavior
Granularity Whole class Per-method
Conflict risk High (module load order) Low (multiple plugins allowed)
Original code Lost Preserved via _super()
Testing Harder Easier

Plugin Types

// Before plugin - runs before method
class BeforePlugin
{
    public function beforeGetName($subject)
    {
        // Runs before getName()
    }
}

// After plugin - runs after method
class AfterPlugin
{
    public function afterGetName($subject, $result)
    {
        // Runs after getName(), can modify result
        return strtoupper($result);
    }
}

// Around plugin - wraps method
class AroundPlugin
{
    public function aroundGetName($subject, callable $proceed)
    {
        // Before logic
        $result = $proceed();  // Call original
        // After logic
        return $result;
    }
}

Best Practice

Prefer plugins over preferences unless you have a specific reason to replace the entire class.

Advanced Preference Patterns

Interface Preference Chain

<!-- Base interface -->
<preference for="Magento\Catalog\Api\ProductRepositoryInterface"
            type="Magento\Catalog\Model\ProductRepository"/>

<!-- Override for specific use case -->
<preference for="Amazon\Prep\Api\ProductRepositoryInterface"
            type="Amazon\Prep\Model\ProductRepository"/>

Preference with Arguments

<preference for="Amazon\Prep\Api\ServiceInterface"
            type="Amazon\Prep\Model\Service"/>

<type name="Amazon\Prep\Model\Service">
    <arguments>
        <argument name="apiKey" xsi:type="string">sk_live_abc</argument>
        <argument name="timeout" xsi:type="number">30</argument>
    </arguments>
</type>

Preference + Plugin Combo

<!-- Preference replaces the class -->
<preference for="Interface" type="NewImplementation"/>

<!-- Plugin extends the new class -->
<type name="NewImplementation">
    <plugin name="extend_new" type="ExtensionPlugin"/>
</type>

Testing with Preferences

// In test, override preference
// etc/di.xml (test area)
<preference for="ServiceInterface" type="MockService"/>

// Or use test fixture
class ServiceTest extends TestCase
{
    public function testService(): void
    {
        $mock = $this->createMock(ServiceInterface::class);
        $mock->method('getData')->willReturn('test');

        $service = new Service($mock);
        $this->assertEquals('test', $service->getData());
    }
}

Common Mistakes

<!-- WRONG: Preference for concrete class -->
<preference for="Magento\Catalog\Model\Product"
            type="Amazon\Prep\Model\Product"/>

<!-- RIGHT: Preference for interface -->
<preference for="Magento\Catalog\Api\ProductInterface"
            type="Amazon\Prep\Model\Product"/>

Debugging Preference Issues

# Check which implementation is being used
bin/magento dev:di:log

# View generated code
cat generated/Amazon/Prep/Model/WarrantyRepository.php

# Check for circular dependencies
bin/magento setup:di:compile 2>&1 | grep -i circular

Quiz

1. When should you use a preference over a plugin?

Question 1 options

2. What happens when two modules define preferences for the same interface?

Question 2 options

3. What is the recommended alternative to preferences for extending behavior?

Question 3 options

Flashcards

Question

What does a preference do?

Answer

Maps an interface to a concrete implementation for DI resolution

Question

When do preference conflicts occur?

Answer

When two modules define preferences for the same interface

Question

Preference vs Plugin: which extends behavior?

Answer

Plugins extend behavior; preferences replace classes entirely

Question

How do you resolve preference conflicts?

Answer

Use plugins instead, or virtual types, or ensure correct module load order

Question

Can you combine preferences and plugins?

Answer

Yes, preference replaces class, plugin extends the new class

Revision Notes

Key Takeaways

  • 1. Preferences map interfaces to implementations globally
  • 2. Area-specific preferences override global ones
  • 3. Preference conflicts occur when multiple modules override the same interface
  • 4. Prefer plugins over preferences for extending behavior
  • 5. Debug preferences with setup:di:compile and dev:di:log

Interview Tips

  • Explain when to use preferences vs plugins
  • Know how to resolve preference conflicts
  • Be ready to discuss the trade-offs of each approach
  • Understand preference resolution in area-specific contexts

Cheat Sheet

Preference:
  <preference for="Interface" type="Implementation"/>

Conflict: last-loaded module wins
Solution: use plugins, virtual types, or correct load order

Plugin:
  <type name="Class">
    <plugin name="name" type="PluginClass"/>
  </type>

Plugin types: before, after, around