Plugins, Preferences, Event Observers & Interceptors
Understanding the core architectural concepts for customizing Magento effectively.
Customization Methods Overview
Methods)) Interceptors Auto-generated Enable plugins generated/ directory ClassName\Interceptor Plugins before Modify inputs Lowest sortOrder first after Modify outputs Highest sortOrder first around Control execution Use sparingly Limitations No final methods No private/protected No static No constructors Preferences Class substitution Implement interfaces Override classes Use sparingly Conflicts possible Event Observers events.xml ObserverInterface execute method Can be disabled Automatic events
1. Interceptors (The Plumbing for Plugins)
What Are Interceptors?
The Interceptor class is the mechanism that makes plugins possible in Magento.
- When the DI system instantiates a class with configured plugins...
- The Object Manager intercepts the process
- Magento appends \Interceptorto the class path
- Example: OrderDatabecomesOrderData\Interceptor
- The Interceptor is auto-generated in the generated/directory
- When a method is called, it executes plugins then calls the original method
Example:
// Original Class
Magento\Sales\Model\Order
// When plugins are configured
Magento\Sales\Model\Order\Interceptor  (auto-generated in generated/)2. Plugins (The Preferred Customization Hook)
Plugins are the recommended tool for adjusting core functionality or the behavior of another module. They allow you to alter input, change output, or stop execution of any public method.
- Non-invasive - don't modify core files
- Multiple plugins can coexist
- Controlled execution order via sortOrder
- Can be disabled without conflicts
Plugin Types
1️⃣ Before Plugins
Purpose: Execute before the observed method runs.
Use Case: Change input parameters sent to the original method.
Sort Order: Lowest to highest
public function beforeSave(
    ProductRepository $subject,
    ProductInterface $product
) {
    // Modify product
    $product->setSku(
        strtoupper($product->getSku())
    );
    return [$product];
}2️⃣ After Plugins
Purpose: Execute after the method completes.
Use Case: Alter the output ($result) returned by the method.
Sort Order: Highest to lowest
public function afterGet(
    ProductRepository $subject,
    ProductInterface $result
) {
    // Modify result
    $result->setCustomAttribute(
        'processed', true
    );
    return $result;
}3️⃣ Around Plugins
Purpose: Determine whether original method executes.
Use Case: Stop execution of all further plugins and original method.
Warning: Use sparingly!
public function aroundSave(
    ProductRepository $subject,
    callable $proceed,
    ProductInterface $product
) {
    // Before original
    $startTime = microtime(true);
    // Call original
    $result = $proceed($product);
    // After original
    $time = microtime(true) - $startTime;
    return $result;
}Plugin Configuration
etc/di.xml
<type name="Magento\Catalog\Model\ProductRepository">
    <plugin name="bonlineco_blog_product_plugin" 
            type="Bonlineco\Blog\Plugin\ProductRepositoryPlugin" 
            sortOrder="10" 
            disabled="false"/>
</type>Attributes:
- name- Unique plugin identifier
- type- Plugin class name
- sortOrder- Execution order (default: 10)
- disabled- Enable/disable plugin (default: false)
Plugin Limitations
Plugins CANNOT Be Applied To:
- Final methods or classes - Cannot be overridden
- Private or protected methods - Not publicly accessible
- Static methods - Called on class, not instance
- Constructors - Use di.xml <type>instead
3. Preferences (Class Substitution)
What Are Preferences?
A preference is an instruction to Magento's DI system to substitute one class for another across the entire application.
Primary Use Cases:
Specify concrete implementation for interfaces.
<preference 
    for="Magento\Catalog\Api\ProductRepositoryInterface"
    type="Magento\Catalog\Model\ProductRepository" />Modify private/protected methods (plugins can't do this).
<preference 
    for="Magento\Checkout\Block\Cart"
    type="Bonlineco\CustomCheckout\Block\Cart" />Use Preferences Sparingly!
Problem: Only ONE preference can exist per class system-wide.
Risk: If two modules try to override the same class with preferences, you'll have a conflict.
Solution: Use plugins whenever possible - they can coexist!
Complete Example:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    
    <!-- Implement interface -->
    <preference for="Bonlineco\Blog\Api\PostRepositoryInterface"
                type="Bonlineco\Blog\Model\PostRepository" />
    
    <!-- Override class (only if you need to modify private/protected methods) -->
    <preference for="Magento\Catalog\Block\Product\View"
                type="Bonlineco\Catalog\Block\Product\View" />
</config>4. Event Observers (The Traditional Hook)
What Are Event Observers?
Event observers provide a way to hook into the Magento application flow at specific, predetermined points where an event is dispatched.
- Events are dispatched at specific points in the code
- Observers "listen" for these events
- When event fires, all registered observers execute
- Observers can be scoped to specific areas (frontend/adminhtml)
Registering an Observer
etc/frontend/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    
    <event name="catalog_product_save_after">
        <observer name="bonlineco_blog_product_save" 
                  instance="Bonlineco\Blog\Observer\ProductSaveObserver"
                  disabled="false" />
    </event>
    
    <event name="customer_login">
        <observer name="bonlineco_blog_customer_login" 
                  instance="Bonlineco\Blog\Observer\CustomerLoginObserver" />
    </event>
    
</config>Attributes:
- name- Event name to listen for
- instance- Observer class
- disabled- Enable/disable observer (optional)
Observer Class Implementation
Observer/ProductSaveObserver.php
<?php
namespace Bonlineco\Blog\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class ProductSaveObserver implements ObserverInterface
{
    /**
     * Execute observer
     *
     * @param Observer $observer
     * @return void
     */
    public function execute(Observer $observer)
    {
        // Get event data
        $product = $observer->getEvent()->getProduct();
        
        // Your custom logic
        if ($product->getSku()) {
            // Do something with the product
            $product->setCustomAttribute('modified_by', 'bonlineco');
        }
    }
}- Must implement ObserverInterface
- Must have an execute(Observer $observer)method
- Access event data via $observer->getEvent()->getData('key')
Disabling Observers
You can disable another module's observer in your own events.xml:
<event name="catalog_product_save_after">
    <observer name="third_party_observer" disabled="true" />
</event>Automatic Model Events
Built-in Events for AbstractModel
Magento automatically triggers events for classes extending \Magento\Framework\Model\AbstractModel (typically database-backed models).
| Event Pattern | When Triggered | Example | 
|---|---|---|
| {prefix}_load_before | Before loading model from DB | catalog_product_load_before | 
| {prefix}_load_after | After loading model from DB | catalog_product_load_after | 
| {prefix}_save_before | Before saving model to DB | catalog_product_save_before | 
| {prefix}_save_after | After saving model to DB | catalog_product_save_after | 
| {prefix}_save_commit_after | After DB transaction commits | catalog_product_save_commit_after | 
| {prefix}_delete_before | Before deleting model from DB | catalog_product_delete_before | 
| {prefix}_delete_after | After deleting model from DB | catalog_product_delete_after | 
{prefix} comes from the model's $_eventPrefix variable.
        Example: Magento\Catalog\Model\Product has $_eventPrefix = 'catalog_product'
Comparison: When to Use Each Method
| Method | Use When | Pros | Cons | 
|---|---|---|---|
| Plugins | Modifying public method behavior | • Multiple can coexist • Controlled order • Non-invasive • Recommended approach | • Can't modify private/protected • Can't use on final/static/constructor | 
| Preferences | • Implementing interfaces • Modifying private/protected methods | • Full class control • Can modify anything | • Only one per class • Conflict-prone • Use sparingly | 
| Event Observers | Reacting to specific application events | • Multiple observers per event • Area-specific • Can be disabled • Loosely coupled | • Limited to dispatched events • Can't modify return values • May impact performance | 
| Interceptors | You don't choose - auto-generated | • Enables plugin system | • Not directly controllable | 
Exam Tips
Key Points to Remember
- Plugins are preferred for most customizations - use them first!
- Know the 3 plugin types and their sortOrder (before: low→high, after: high→low)
- Remember plugin limitations: no final, private, protected, static, or constructors
- Preferences should be used sparingly - only ONE can exist per class
- Interceptors are auto-generated in generated/- you never write them
- Observers must implement ObserverInterfaceand haveexecute()method
- Know the automatic model events: _load_before/after,_save_before/after/commit_after,_delete_before/after
- Observers are registered in events.xml, can be scoped to areas
- Around plugins should be used sparingly due to performance impact
- Before plugins return parameters as array, after plugins return modified result
