Skip to content
intermediate Phase 63 · Extension Points

Events Deep Dive — Dispatching Internals

Understanding Magento 2 event dispatching internals, event data handling, observer execution order, and event debugging techniques

45m
2 problems
Topic Progress 0%

Event Dispatching Internals

The ManagerInterface

Magento dispatches events through Magento\Framework\Event\ManagerInterface. The concrete implementation is Magento\Framework\Event\Manager.

$eventManager->dispatch(
    'catalog_product_save_after',
    ['product' => $product, 'controller' => $this]
);

What Happens During dispatch()

  1. The Event Manager creates an Event object
  2. Event data is merged into the Event object
  3. All observers registered for the event name are collected
  4. Observers are sorted by sort_order from events.xml
  5. Each observer's execute() is called in order
  6. The Event object (possibly modified) is returned
// Simplified internal flow
public function dispatch($eventName, array $data = [])
{
    $event = $this->eventFactory->create();
    $event->setName($eventName);
    $event->setData($data);
    
    $observers = $this->config->getObservers($eventName);
    $observers = $this->sortObservers($observers);
    
    foreach ($observers as $observerConfig) {
        $observer = $this->createObserver($observerConfig);
        $observer->dispatch($event);
    }
    
    return $event;
}

Event Propagation

Events propagate across areas. A global events.xml registers observers for all areas. Area-specific files add or override observers for that area.

<!-- etc/events.xml (global) -->
<event name="catalog_product_save_after">
    <observer name="global_log" instance="Vendor\Logger\Observer\Log"/>
</event>

<!-- etc/adminhtml/events.xml (admin only) -->
<event name="catalog_product_save_after">
    <observer name="admin_audit" instance="Vendor\Audit\Observer\AdminAudit"/>
</event>

Dispatching Events from Different Areas

Events dispatched in adminhtml area only trigger adminhtml + global observers. Frontend events only trigger frontend + global observers.

// In a frontend controller — frontend + global observers fire
$this->eventManager->dispatch('my_custom_event', ['data' => $data]);

// In an admin controller — adminhtml + global observers fire
$this->eventManager->dispatch('my_custom_event', ['data' => $data]);

Event Data Handling

The Event Object

Magento\Framework\Event\Event extends Magento\Framework\DataObject. This means it supports dynamic getters/setters.

// Data passed during dispatch
$this->eventManager->dispatch('order_place_after', [
    'order' => $order,
    'quote' => $quote,
    'custom_value' => 42
]);

// In observer — accessing data
$order = $observer->getEvent()->getOrder();
$quote = $observer->getEvent()->getQuote();
$value = $observer->getEvent()->getCustomValue();
$allData = $observer->getEvent()->getData();

Modifying Event Data

Observers can modify event data. Changes persist to subsequent observers.

// Observer 1 — modifies the product
public function execute(Observer $observer)
{
    $product = $observer->getEvent()->getProduct();
    $product->setName($product->getName() . ' (Modified)');
    // This change is visible to Observer 2
}

// Observer 2 — sees the modified product
public function execute(Observer $observer)
{
    $product = $observer->getEvent()->getProduct();
    echo $product->getName(); // 'Product Name (Modified)'
}

Passing Scalar Values

Scalar values in events are passed by value. Modifying them in an observer does not affect other observers.

$this->eventManager->dispatch('calculate_discount', [
    'amount' => 100,
    'discount' => 0
]);

// Observer 1
public function execute(Observer $observer)
{
    $discount = $observer->getEvent()->getDiscount();
    $observer->getEvent()->setDiscount($discount + 10);
    // This change is visible to other observers via DataObject
}

Best Practices for Event Data

// Pass full objects, not IDs — observers may need relationships
$this->eventManager->dispatch('order_shipped', [
    'order' => $order,  // Good
    'order_id' => $order->getId()  // Avoid — forces observer to re-load
]);

// Name data clearly
$this->eventManager->dispatch('inventory_deducted', [
    'product' => $product,
    'qty_deducted' => $qty,
    'source' => $source
]);

Observer Execution Order

sort_order in events.xml

Observers execute based on sort_order attribute. Lower values execute first.

<event name="catalog_product_save_after">
    <observer name="first" instance="Vendor\First\Observer\Run"
              sort_order="10"/>
    <observer name="second" instance="Vendor\Second\Observer\Run"
              sort_order="20"/>
    <observer name="third" instance="Vendor\Third\Observer\Run"
              sort_order="30"/>
</event>

Default sort_order

If no sort_order is specified, observers execute in the order they are loaded — which depends on module load order (alphabetical by module name, then config merge order).

<!-- These run in unpredictable order -->
<observer name="a" instance="Vendor\A\Observer\Run"/>
<observer name="b" instance="Vendor\B\Observer\Run"/>

<!-- These run in predictable order -->
<observer name="a" instance="Vendor\A\Observer\Run" sort_order="100"/>
<observer name="b" instance="Vendor\B\Observer\Run" sort_order="200"/>

Cross-Module Observer Ordering

When multiple modules register observers for the same event, execution order depends on:

  1. Module load order (app/etc/config.php)
  2. sort_order values across modules
  3. Config file merge order
// Check module load order
php bin/magento module:status
php bin/magento setup:module:status

// Check events configuration
grep -r 'catalog_product_save_after' vendor/*/etc/events.xml

Shared Sort Order Ranges

Follow conventions to avoid conflicts:

Range Purpose
10-99 Core Magento observers
100-199 Third-party extension observers
200-299 Custom module observers
300-999 Late-execution observers
<!-- Your module's observer with explicit sort_order -->
<event name="checkout_cart_product_add_after">
    <observer name="my_enhancement"
              instance="Vendor\Enhance\Observer\CartEnhance"
              sort_order="150"/>
</event>

Event Debugging Techniques

Logging All Dispatched Events

Create a module to log all events:

<!-- Vendor/EventLogger/etc/events.xml -->
<event name="*">
    <observer name="event_logger"
              instance="Vendor\EventLogger\Observer\LogAll"/>
</event>
namespace Vendor\EventLogger\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;

class LogAll implements ObserverInterface
{
    public function __construct(
        private LoggerInterface $logger
    ) {}

    public function execute(Observer $observer)
    {
        $eventName = $observer->getEvent()->getName();
        $this->logger->debug('Event dispatched: ' . $eventName, [
            'data' => $observer->getEvent()->getData()
        ]);
    }
}

Xdebug Profiling for Events

Set breakpoints in Magento\Framework\Event\Manager::dispatch() to trace events:

// In Manager.php or via IDE breakpoint
public function dispatch($eventName, array $data = [])
{
    // Breakpoint here — inspect $eventName and $data
    // Step through to see observer execution
}

Magento Event Profiler

Enable event profiling in app/etc/env.php:

return [
    'system' => [
        'logs' => [
            'event_profiling' => [
                'type' => 'file',
                'file' => 'var/log/event_profile.log'
            ]
        ]
    ]
];

Debugging Observer Loading

// Check which observers are registered for an event
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$config = $objectManager->get(\Magento\Framework\Event\Config\Reader::class);
$observers = $config->getObservers('catalog_product_save_after');
var_dump($observers);

// Or check via CLI
php bin/magento dev:events:list
php bin/magento dev:events:watch catalog_product_save_after

Practice Problems

0 / 2 solved
Observer Order Conflict

Two modules register observers for the same event without sort_order. Identify the problem and fix it.

Event Data Not Propagating

An observer modifies an event object, but a subsequent observer doesn't see the change. Diagnose and fix.

Quiz

1. What attribute controls observer execution order?

Question 1 options

2. What class does Magento\Framework\Event\Event extend?

Question 2 options

3. If two modules register observers for the same event without sort_order, what determines execution order?

Question 3 options

4. How can you log all dispatched events?

Question 4 options

Flashcards

Question

What is sort_order in events.xml?

Answer

An attribute controlling observer execution order; lower values execute first

Question

What class does the Event object extend?

Answer

Magento\Framework\DataObject — providing dynamic getters and setters

Question

Can observers modify event data visible to subsequent observers?

Answer

Yes, observers can modify the Event object and changes persist to later observers in the chain

Question

What determines observer order when sort_order is not set?

Answer

Module load order from app/etc/config.php merge sequence

Question

How do you debug which observers are registered for an event?

Answer

php bin/magento dev:events:watch <event_name>

Revision Notes

Key Takeaways

  • 1. Event dispatching creates an Event object, collects observers, sorts by sort_order, and executes each
  • 2. Event extends DataObject — changes to the Event object persist across observers
  • 3. sort_order attribute controls execution order; lower values execute first
  • 4. Area-specific events.xml adds observers for that area only; global events.xml applies everywhere
  • 5. Debug with wildcard observers, Xdebug breakpoints in Manager::dispatch(), and dev:events:watch CLI

Interview Tips

  • Explain the full lifecycle of event dispatching from dispatch() call to observer execution
  • Discuss how sort_order resolves observer execution conflicts
  • Explain why passing full objects is better than passing IDs in event data
  • Describe debugging approaches for event-driven issues

Cheat Sheet

Events Deep Dive Cheat Sheet

dispatch() flow:

  1. Create Event object with data
  2. Collect observers from events.xml
  3. Sort by sort_order (lower first)
  4. Execute each observer sequentially

sort_order ranges:

  • 10-99: Core
  • 100-199: Third-party
  • 200-299: Custom

Debug tools:

  • Wildcard observer: <event name="*">
  • CLI: dev:events:watch <event>
  • Xdebug breakpoint in Manager::dispatch()

Event data:

  • Extends DataObject — dynamic getters/setters
  • Changes persist to subsequent observers
  • Pass full objects, not just IDs