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?
2. How are preference conflicts resolved between modules?
3. What is a virtual type in Magento DI?
Flashcards
Question
Where are preferences defined?
Click to reveal answer
Answer
In etc/di.xml at the module root level (global scope)
Question
What is the benefit of using interfaces with preferences?
Click to reveal answer
Answer
Loose coupling - the implementation can be swapped without changing dependent code
Question
What is a better alternative to preferences for method behavior changes?
Click to reveal answer
Answer
Plugins (interceptors)
Question
When should you use virtual types over preferences?
Click to reveal answer
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:
- Plugins (first choice for behavior changes)
- Virtual Types (for configuration variants)
- Preferences (only for full replacement)