BehavioralPHPverifiedVerified
Observer Pattern in PHP
Defines a one-to-many dependency so that when one object changes state, all its dependents are notified and updated automatically.
How to Implement the Observer Pattern in PHP
1Step 1: Define the observer and subject interfaces
interface Observer
{
public function update(string $event, mixed $data): void;
}
interface Subject
{
public function subscribe(string $event, Observer $observer): void;
public function unsubscribe(string $event, Observer $observer): void;
public function notify(string $event, mixed $data): void;
}2Step 2: Implement a concrete event emitter
class EventEmitter implements Subject
{
/** @var array<string, Observer[]> */
private array $listeners = [];
public function subscribe(string $event, Observer $observer): void
{
$this->listeners[$event][] = $observer;
}
public function unsubscribe(string $event, Observer $observer): void
{
$this->listeners[$event] = array_filter(
$this->listeners[$event] ?? [],
fn(Observer $o) => $o !== $observer,
);
}
public function notify(string $event, mixed $data): void
{
foreach ($this->listeners[$event] ?? [] as $observer) {
$observer->update($event, $data);
}
}
}3Step 3: Implement concrete observers
class Logger implements Observer
{
public function update(string $event, mixed $data): void
{
echo "[LOG] {$event}: " . json_encode($data) . "\n";
}
}
class Mailer implements Observer
{
public function update(string $event, mixed $data): void
{
echo "[MAIL] Notification for {$event}\n";
}
}
// Usage
$emitter = new EventEmitter();
$emitter->subscribe('order.created', new Logger());
$emitter->subscribe('order.created', new Mailer());
$emitter->notify('order.created', ['orderId' => 42]);<?php
declare(strict_types=1);
// [step] Define typed event interfaces with generics-like approach
interface EventInterface
{
public function getName(): string;
public function getPayload(): array;
public function getTimestamp(): float;
}
interface EventListenerInterface
{
public function handle(EventInterface $event): void;
public function getPriority(): int;
}
interface EventDispatcherInterface
{
public function listen(string $eventName, EventListenerInterface $listener): void;
public function dispatch(EventInterface $event): void;
public function removeListener(string $eventName, EventListenerInterface $listener): void;
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
// [step] Implement a base event class
class Event implements EventInterface
{
private readonly float $timestamp;
/** @param array<string, mixed> $payload */
public function __construct(
private readonly string $name,
private readonly array $payload = [],
) {
$this->timestamp = microtime(true);
}
public function getName(): string { return $this->name; }
public function getPayload(): array { return $this->payload; }
public function getTimestamp(): float { return $this->timestamp; }
}
// [step] Implement the event dispatcher with priority queues and error isolation
final class EventDispatcher implements EventDispatcherInterface
{
/** @var array<string, EventListenerInterface[]> */
private array $listeners = [];
/** @var array<string, bool> */
private array $sorted = [];
public function __construct(
private readonly LoggerInterface $logger,
) {}
public function listen(string $eventName, EventListenerInterface $listener): void
{
$this->listeners[$eventName][] = $listener;
$this->sorted[$eventName] = false;
}
public function dispatch(EventInterface $event): void
{
$name = $event->getName();
$this->sortListeners($name);
$this->logger->info("Dispatching event", [
'event' => $name,
'listenerCount' => count($this->listeners[$name] ?? []),
]);
foreach ($this->listeners[$name] ?? [] as $listener) {
try {
$listener->handle($event);
} catch (\Throwable $e) {
$this->logger->error("Listener failed", [
'event' => $name,
'listener' => $listener::class,
'error' => $e->getMessage(),
]);
// Continue dispatching to other listeners
}
}
}
public function removeListener(string $eventName, EventListenerInterface $listener): void
{
$this->listeners[$eventName] = array_values(array_filter(
$this->listeners[$eventName] ?? [],
fn(EventListenerInterface $l) => $l !== $listener,
));
}
private function sortListeners(string $eventName): void
{
if (($this->sorted[$eventName] ?? true)) return;
usort(
$this->listeners[$eventName],
fn(EventListenerInterface $a, EventListenerInterface $b) => $b->getPriority() - $a->getPriority(),
);
$this->sorted[$eventName] = true;
}
}
// [step] Example concrete listeners
final class AuditLogListener implements EventListenerInterface
{
public function __construct(private readonly LoggerInterface $logger) {}
public function handle(EventInterface $event): void
{
$this->logger->info("Audit: {$event->getName()}", $event->getPayload());
}
public function getPriority(): int { return 100; }
}
final class NotificationListener implements EventListenerInterface
{
public function handle(EventInterface $event): void
{
// Send notification based on event type
}
public function getPriority(): int { return 50; }
}Observer Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Observer Pattern in the Real World
“Think of a newspaper subscription service. The publisher (subject) doesn't know exactly who its subscribers (observers) are—it just maintains a list. When a new edition is printed, it delivers a copy to every subscriber on the list automatically. Subscribers can cancel at any time without the publisher needing to change anything.”