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?
2. What happens when two modules define preferences for the same interface?
3. What is the recommended alternative to preferences for extending behavior?
Flashcards
Question
What does a preference do?
Click to reveal answer
Answer
Maps an interface to a concrete implementation for DI resolution
Question
When do preference conflicts occur?
Click to reveal answer
Answer
When two modules define preferences for the same interface
Question
Preference vs Plugin: which extends behavior?
Click to reveal answer
Answer
Plugins extend behavior; preferences replace classes entirely
Question
How do you resolve preference conflicts?
Click to reveal answer
Answer
Use plugins instead, or virtual types, or ensure correct module load order
Question
Can you combine preferences and plugins?
Click to reveal answer
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