Observer Class Patterns
Basic Observer Structure
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();
return $this;
}
}
Observer with Dependencies
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;
class InventorySync implements ObserverInterface
{
public function __construct(
private LoggerInterface $logger
) {}
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
$this->logger->info('Sync: ' . $product->getSku());
return $this;
}
}
Stateless Observers
Observers should be stateless. No instance variables that hold state between calls:
// Bad: stateful observer
class BadObserver implements ObserverInterface
{
private $processedIds = [];
public function execute(Observer $observer)
{
$this->processedIds[] = $observer->getEvent()->getProduct()->getId();
}
}
// Good: stateless observer
class GoodObserver implements ObserverInterface
{
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
// Process without storing state
}
}
Shared Observers
Handling Multiple Events
A single observer class can handle multiple events:
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class ProductEventHandler implements ObserverInterface
{
public function execute(Observer $observer)
{
$eventName = $observer->getEvent()->getName();
match($eventName) {
'catalog_product_save_before' => $this->handleBeforeSave($observer),
'catalog_product_save_after' => $this->handleAfterSave($observer),
default => null
};
return $this;
}
private function handleBeforeSave(Observer $observer): void { /* ... */ }
private function handleAfterSave(Observer $observer): void { /* ... */ }
}
Registering the Shared Observer
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_product_save_before">
<observer name="handler" instance="Vendor\Module\Observer\ProductEventHandler"/>
</event>
<event name="catalog_product_save_after">
<observer name="handler" instance="Vendor\Module\Observer\ProductEventHandler"/>
</event>
</config>
Benefits
- Reduces class count
- Groups related logic
- Easier to maintain consistent behavior
- Reduces ObjectManager overhead
Event Arguments and Return Values
Passing Complex Arguments
$this->eventManager->dispatch('order_calculate_discount', [
'order' => $order,
'items' => $order->getAllItems(),
'discount_amount' => 0
]);
Reading Arguments in Observers
public function execute(Observer $observer)
{
$order = $observer->getEvent()->getOrder();
$discount = $observer->getEvent()->getDiscountAmount();
$observer->getEvent()->setDiscountAmount($discount + 10);
}
Collecting Results
$event = $this->eventManager->dispatch('calculate_discount', [
'order' => $order, 'discount' => 0
]);
$finalDiscount = $event->getDiscount();
Exception Handling
public function execute(Observer $observer)
{
try {
$this->service->process();
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
$observer->getEvent()->setError($e->getMessage());
}
return $this;
}
Observers return $this and cannot return values. Modify event data to communicate results.
Observer Performance Optimization
Lazy Loading
public function __construct(
private LoggerInterface $logger,
private \Magento\Framework\ObjectManager\ObjectManager $objectManager
) {}
public function execute(Observer $observer)
{
if ($this->isRelevant($observer)) {
$service = $this->objectManager->get(HeavyService::class);
}
}
Avoid Heavy Logic
// Bad: synchronous heavy processing
public function execute(Observer $observer)
{
$this->generateReport();
$this->sendEmails();
}
// Good: dispatch to async queue
public function execute(Observer $observer)
{
$this->publisher->publish('product.save.after', [
'product_id' => $observer->getEvent()->getProduct()->getId()
]);
}
Batch Processing
public function execute(Observer $observer)
{
$batch = $this->cache->load('product_updates');
$batch = $batch ? json_decode($batch, true) : [];
$batch[] = $observer->getEvent()->getProduct()->getId();
$this->cache->save(json_encode($batch), 'product_updates', [], 300);
if (count($batch) >= 50) {
$this->processBatch($batch);
}
}
Profiling
public function execute(Observer $observer)
{
$start = microtime(true);
// ... logic ...
$elapsed = microtime(true) - $start;
if ($elapsed > 0.1) {
$this->logger->warning('Slow observer: ' . round($elapsed, 3) . 's');
}
}
Practice Problems
A shared observer accumulates data in a private property across dispatches, causing memory exhaustion. Fix the pattern.
Quiz
1. Why should observers be stateless?
2. Can an observer return a value to the event dispatcher?
3. What is a shared observer?
Flashcards
Question
Why must observers be stateless?
Click to reveal answer
Answer
Observer instances may be reused; stateful observers leak memory and cause bugs
Question
Can observers return values?
Click to reveal answer
Answer
No; use event data modification to communicate results
Question
What is a shared observer pattern?
Click to reveal answer
Answer
A single observer class registered for multiple events, routing by event name
Question
How to handle heavy processing in observers?
Click to reveal answer
Answer
Dispatch to message queues for async processing
Question
How to profile observer execution?
Click to reveal answer
Answer
Measure microtime(true) before/after and log warnings for slow observers
Revision Notes
Key Takeaways
- 1. Observers implement ObserverInterface with execute(Observer $observer)
- 2. Observers must be stateless to prevent memory leaks
- 3. Shared observers handle multiple events by checking event name
- 4. Observers communicate results by modifying Event data, not return values
- 5. Move heavy processing to async message queues
- 6. Profile observer execution time and log slow observers
Interview Tips
- • Explain the stateless observer pattern and why it matters
- • Discuss shared observers as a code organization pattern
- • Explain how observer results propagate via Event data modification
- • Describe performance optimization strategies for observers
Cheat Sheet
Observers Deep Dive Cheat Sheet
Class structure:
class MyObserver implements ObserverInterface {
public function execute(Observer $observer) {
$data = $observer->getEvent()->getData();
return $this;
}
}
Shared observer:
match($observer->getEvent()->getName()) { ... }
Performance:
- Stateless, no instance variables
- Lazy load dependencies
- Async heavy processing
- Profile with microtime(true)