Observer

The Observer pattern defines a one-to-many dependency between objects: when one object — the subject — changes state, all of its dependents — the observers — are notified automatically, without the subject needing to know anything about what each observer actually does with that notification.

The Problem It Solves

Imagine placing an order needs to trigger several unrelated side effects: send a confirmation email, update inventory counts, and maybe log the sale for analytics. The naive approach hardcodes every one of those calls directly inside the order-placing method:

<?php

class Order {
    public function place(): void {
        // ...order placement logic...

        // Every new side effect means editing this method directly
        (new EmailNotifier())->send($this);
        (new InventoryUpdater())->reduceStock($this);
    }
}

Order now has to know the exact class names and method signatures of every single thing that cares about a new order, and adding a new dependent (say, a Slack notification for the sales team) means opening and editing Order again. The subject and its many unrelated dependents have become tightly, needlessly coupled.

What Observer Actually Does

Observer decouples the subject from its dependents by routing through a shared interface:

  • The subject keeps a list of observers and exposes attach() / detach() to add or remove them.
  • When something noteworthy happens, the subject calls its own notify() method.
  • notify() simply loops over every attached observer and calls a shared interface method (commonly update()) on each one.
  • The subject never knows or cares what each observer actually does inside update().

A Realistic Worked Example: Order Notifications

First, the interface every observer must implement:

<?php

interface OrderObserverInterface {
    public function update(Order $order): void;
}

The Order subject holds the list of observers and drives the notifications:

<?php

class Order {
    public string $id;
    public float $total;
    private array $observers = [];

    public function __construct(string $id, float $total) {
        $this->id = $id;
        $this->total = $total;
    }

    public function attach(OrderObserverInterface $observer): void {
        $this->observers[] = $observer;
    }

    public function detach(OrderObserverInterface $observer): void {
        $this->observers = array_filter(
            $this->observers,
            fn($o) => $o !== $observer
        );
    }

    public function notify(): void {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function place(): void {
        // ...order placement logic...
        $this->notify(); // Order has no idea who is listening, or how many
    }
}

And two independent observers, each implementing the same interface, doing completely unrelated work:

<?php

class EmailNotifier implements OrderObserverInterface {
    public function update(Order $order): void {
        mail(
            'customer@example.com',
            "Order {$order->id} confirmed",
            "Your order total was {$order->total}"
        );
    }
}

class InventoryUpdater implements OrderObserverInterface {
    public function update(Order $order): void {
        // Reduce stock levels for every item in $order
    }
}

// Usage — Order doesn't reference either class by name
$order = new Order('ORD-1042', 79.99);
$order->attach(new EmailNotifier());
$order->attach(new InventoryUpdater());
$order->place(); // notify() fires update() on both observers
Adding a Slack notification later: write a SlackNotifier class implementing OrderObserverInterface and attach() it. Order, EmailNotifier, and InventoryUpdater stay exactly as they are.

Real-World PHP: This Is What Event Systems Are Built On

You've almost certainly used this exact idea already, just with friendlier names. Laravel's Events and Listeners system is a full-featured Observer: an OrderPlaced event is the subject's notification, and every registered listener class is an observer that reacts to it without the code that fired the event knowing any of them exist. Symfony's EventDispatcher component works the same way — components dispatch named events, and any number of independently-registered subscribers can react. Both frameworks essentially generalized attach()/notify() into a registry keyed by event name, but the underlying idea — decoupled one-to-many notification — is exactly the Observer pattern.

When to Reach for Observer

  • An action needs to trigger several independent side effects that shouldn't know about each other.
  • You want to add or remove reactions to an event without touching the class that triggers it.
  • Different parts of an application need to stay in sync with a shared piece of state.
Up next: Command looks at a related but distinct problem — turning an action itself into an object, so it can be queued, logged, or undone.