1.04

Plugins, Preferences, Event Observers & Interceptors

Understanding the core architectural concepts for customizing Magento effectively.

Why This Matters: These four components are the foundation of Magento customization. Knowing when and how to use each one is critical for the exam and real-world development.

Customization Methods Overview

mindmap root((Customization
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.

How It Works:
  1. When the DI system instantiates a class with configured plugins...
  2. The Object Manager intercepts the process
  3. Magento appends \Interceptor to the class path
  4. Example: OrderData becomes OrderData\Interceptor
  5. The Interceptor is auto-generated in the generated/ directory
  6. 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/)
Key Point: Interceptors are automatically created by Magento. You never write them manually - they're generated when you define plugins in di.xml.

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.

✅ Why Plugins Are Preferred:
  • 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];
}
Note: Return parameters as an array!
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;
}
Note: Receives original parameters too!
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;
}
Warning: Can cause unintended consequences!

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:
1. Implement Interfaces

Specify concrete implementation for interfaces.

<preference 
    for="Magento\Catalog\Api\ProductRepositoryInterface"
    type="Magento\Catalog\Model\ProductRepository" />
2. Override Classes

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.

Key Concepts:
  • 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');
        }
    }
}
Requirements:
  • 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
Event Prefix: The {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 ObserverInterface and have execute() 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