Skip to content
intermediate Phase 63 · Extension Points

Observers Deep Dive — Classes, Shared Observers, and Performance

Advanced observer patterns in Magento 2: observer classes, shared observers, event arguments handling, and observer performance optimization

45m
1 problems
Topic Progress 0%

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

0 / 1 solved
Observer Memory Leak

A shared observer accumulates data in a private property across dispatches, causing memory exhaustion. Fix the pattern.

Quiz

1. Why should observers be stateless?

Question 1 options

2. Can an observer return a value to the event dispatcher?

Question 2 options

3. What is a shared observer?

Question 3 options

Flashcards

Question

Why must observers be stateless?

Answer

Observer instances may be reused; stateful observers leak memory and cause bugs

Question

Can observers return values?

Answer

No; use event data modification to communicate results

Question

What is a shared observer pattern?

Answer

A single observer class registered for multiple events, routing by event name

Question

How to handle heavy processing in observers?

Answer

Dispatch to message queues for async processing

Question

How to profile observer execution?

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)