Event-Driven Architecture in Magento
Why Events?
Magento 2 uses an event-driven architecture to allow modules to communicate without direct dependencies. Instead of Module A calling Module B directly, Module A dispatches an event, and any module can listen to it via an observer.
This decouples modules — Module A doesn't know or care who's listening.
How Events Work
Code dispatches event → Event Manager notifies observers → Observers execute their logic
The dispatch call:
$eventManager = $this->eventManager;
$eventManager->dispatch('catalog_product_save_after', ['product' => $product]);
The observer configuration in events.xml:
<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="my_observer" instance="Vendor\MyModule\Observer\ProductAfterSave"/>
</event>
</config>
Event Naming Convention
Events follow a pattern: {entity}_{action}_{timing}
catalog_product_save_after— after a catalog product is savedsales_order_place_before— before an order is placedcustomer_login— when a customer logs in
The timing suffix (_before, _after) indicates when the observer fires relative to the main action.
areas.xml
Events can be area-specific. Place events.xml in:
etc/events.xml— global (all areas)etc/frontend/events.xml— frontend onlyetc/adminhtml/events.xml— admin onlyetc/crontab/events.xml— cron only
This ensures observers only run in the appropriate area.
Configuring events.xml
Basic events.xml Structure
<?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="unique_observer_name"
instance="Vendor\Module\Observer\ProductObserver"
disabled="false"/>
</event>
</config>
Observer Attributes
| Attribute | Required | Description |
|---|---|---|
| name | Yes | Unique name for this observer within the event |
| instance | Yes | Fully qualified class name of the observer |
| disabled | No | Set to true to disable the observer (default: false) |
Multiple Observers per Event
A single event can have multiple observers:
<event name="catalog_product_save_after">
<observer name="inventory_sync"
instance="Vendor\Inventory\Observer\SyncInventory"/>
<observer name="search_reindex"
instance="Vendor\Search\Observer\ReindexProduct"/>
<observer name="audit_log"
instance="Vendor\Audit\Observer\LogProductChange"/>
</event>
Observers execute in the order they are loaded, which depends on module load order.
Disabling Observers
Temporarily disable an observer without removing it:
<observer name="debug_logger"
instance="Vendor\Debug\Observer\Logger"
disabled="true"/>
This is useful for:
- Debugging without observer side effects
- Temporarily disabling third-party observer behavior
- Performance testing with specific observers turned off
Creating Observer Classes
Observer Class Structure
Observers implement Magento\Framework\Event\ObserverInterface:
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class ProductAfterSave implements ObserverInterface
{
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
// Your logic here
return $this;
}
}
Accessing Event Data
The Observer object provides access to data passed during dispatch:
// Dispatched with: $this->dispatch('sales_order_place_before', ['order' => $order]);
public function execute(Observer $observer)
{
$order = $observer->getEvent()->getOrder();
// or
$order = $observer->getEvent()->getData('order');
$orderId = $order->getIncrementId();
$total = $order->getGrandTotal();
// ... process order data
}
Observer with Dependencies
Observers can receive dependencies via constructor injection:
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;
use Vendor\Module\Service\NotificationService;
class OrderPlaced implements ObserverInterface
{
public function __construct(
private LoggerInterface $logger,
private NotificationService $notificationService
) {}
public function execute(Observer $observer)
{
$order = $observer->getEvent()->getOrder();
$this->logger->info('Order placed: ' . $order->getIncrementId());
$this->notificationService->notifyNewOrder($order);
return $this;
}
}
Common Magento Events
| Event Name | When Dispatched |
|---|---|
| catalog_product_save_after | After product save |
| catalog_product_save_before | Before product save |
| customer_login | Customer authenticates |
| customer_register_success | After customer registration |
| sales_order_place_before | Before order placement |
| sales_order_save_after | After order save |
| checkout_cart_product_add_after | After adding item to cart |
| controller_action_predispatch | Before any controller action |
| controller_action_postdispatch | After any controller action |
Always check existing events in vendor/magento/ before creating custom ones.
Event Dispatching and Best Practices
Dispatching Custom Events
Create your own events for other modules to observe:
namespace Vendor\Module\Service;
class WarrantyService
{
public function __construct(
private \Magento\Framework\Event\ManagerInterface $eventManager
) {}
public function processWarranty($warranty)
{
// Before processing
$this->eventManager->dispatch(
'vendor_warranty_process_before',
['warranty' => $warranty]
);
// Process warranty
$result = $this->doProcessing($warranty);
// After processing
$this->eventManager->dispatch(
'vendor_warranty_process_after',
['warranty' => $warranty, 'result' => $result]
);
return $result;
}
}
Best Practices
- Use descriptive event names —
vendor_module_action_timingpattern - Always pass relevant objects — observers need data to work with
- Keep observers lightweight — heavy logic belongs in services
- Don't modify dispatched objects unexpectedly — observers should not break assumptions
- Document your events — other developers need to know what data is passed
- Use area-specific events.xml — don't load frontend observers in admin
Performance Considerations
Every dispatched event adds overhead. Avoid dispatching events in tight loops:
// Bad: dispatches event for every product in a loop
foreach ($products as $product) {
$this->eventManager->dispatch('product_processed', ['product' => $product]);
}
// Better: dispatch once after the loop
$this->eventManager->dispatch('products_batch_processed', ['products' => $products]);
Debugging Events
To see which events are dispatched, add logging:
// In a custom module, observe controller_action_predispatch
public function execute(Observer $observer)
{
$action = $observer->getEvent()->getControllerAction();
$this->logger->info('Event dispatched for: ' . get_class($action));
}
Or check the event list in the Magento codebase:
grep -r "dispatch('" vendor/magento/ --include="*.php" | head -50
Quiz
1. What interface must an observer class implement?
2. How do you temporarily disable an observer in events.xml?
3. Which events.xml location makes an observer run only in the admin panel?
Flashcards
Question
What does events.xml configure?
Click to reveal answer
Answer
The mapping between event names and observer classes that listen to those events
Question
How do you access event data in an observer?
Click to reveal answer
Answer
$observer->getEvent()->getProductName() or $observer->getEvent()->getData('name')
Question
What is the timing suffix pattern for events?
Click to reveal answer
Answer
_before (before action) and _after (after action)
Question
Where do area-specific events.xml files go?
Click to reveal answer
Answer
etc/frontend/events.xml, etc/adminhtml/events.xml, etc/crontab/events.xml
Question
What is the observer class requirement?
Click to reveal answer
Answer
Must implement Magento\Framework\Event\ObserverInterface with an execute() method
Revision Notes
Key Takeaways
- 1. events.xml maps event names to observer classes
- 2. Observers implement ObserverInterface and receive event data via the Observer object
- 3. Events can be scoped to areas: frontend, adminhtml, crontab
- 4. Use disabled='true' to temporarily turn off observers
- 5. Follow naming convention: entity_action_timing (e.g., product_save_after)
Interview Tips
- • Explain the observer pattern and how it decouples modules
- • Know how to access event data in an observer
- • Discuss when to use events vs direct service calls
- • Be aware of performance implications of dispatching events in loops
Cheat Sheet
events.xml:
<event name="entity_action_timing">
<observer name="unique_name"
instance="Vendor\Module\Observer\ClassName"
disabled="false"/>
</event>
Observer:
implements ObserverInterface
execute(Observer $observer):
$observer->getEvent()->getProduct()
Area-specific:
etc/frontend/events.xml
etc/adminhtml/events.xml