<?php
/**
* Observer Pattern en PHP 8.1+ con WeakReference
* Ejemplo práctico: carrito de compra con eventos
*/
declare(strict_types=1);
// ?????????????????????????????????????????????????????????????
// Enums para los tipos de evento (PHP 8.1)
// ?????????????????????????????????????????????????????????????
enum EventType: string
{
case ItemAdded = 'item.added';
case OrderPlaced = 'order.placed';
case StockUpdated = 'stock.updated';
}
// ?????????????????????????????????????????????????????????????
// Contrato base para todos los eventos del dominio
// ?????????????????????????????????????????????????????????????
abstract class DomainEvent
{
public readonly DateTimeImmutable $occurredAt;
public function __construct()
{
$this->occurredAt = new DateTimeImmutable();
}
abstract public function type(): EventType;
}
class ItemAddedEvent extends DomainEvent
{
public function __construct(
public readonly string $productName,
public readonly int $quantity,
public readonly float $unitPrice
) { parent::__construct(); }
public function type(): EventType { return EventType::ItemAdded; }
}
class OrderPlacedEvent extends DomainEvent
{
public function __construct(
public readonly string $orderId,
public readonly float $total,
public readonly string $customerEmail
) { parent::__construct(); }
public function type(): EventType { return EventType::OrderPlaced; }
}
class StockUpdatedEvent extends DomainEvent
{
public function __construct(
public readonly string $productName,
public readonly int $newStock
) { parent::__construct(); }
public function type(): EventType { return EventType::StockUpdated; }
}
// ?????????????????????????????????????????????????????????????
// Interfaz del observer (listener)
// ?????????????????????????????????????????????????????????????
interface EventListener
{
public function handle(DomainEvent $event): void;
public function listensTo(): EventType;
}
// ?????????????????????????????????????????????????????????????
// EventEmitter con WeakReference para evitar memory leaks
// WeakReference no impide que el GC libere al listener
// si el objeto ya no tiene otras referencias fuertes.
// ?????????????????????????????????????????????????????????????
class EventEmitter
{
/** @var array<string, list<WeakReference<EventListener>>> */
private array $listeners = [];
public function subscribe(EventListener $listener): void
{
$key = $listener->listensTo()->value;
$this->listeners[$key][] = WeakReference::create($listener);
}
public function emit(DomainEvent $event): void
{
$key = $event->type()->value;
if (!isset($this->listeners[$key])) return;
foreach ($this->listeners[$key] as $i => $ref) {
$listener = $ref->get();
if ($listener === null) {
// El listener fue recogido por el GC: limpiar la referencia muerta
unset($this->listeners[$key][$i]);
continue;
}
$listener->handle($event);
}
}
}
// ?????????????????????????????????????????????????????????????
// Listeners concretos
// ?????????????????????????????????????????????????????????????
class EmailNotificationListener implements EventListener
{
public function listensTo(): EventType { return EventType::OrderPlaced; }
public function handle(DomainEvent $event): void
{
/** @var OrderPlacedEvent $event */
echo "[EMAIL] Enviando confirmación de pedido #{$event->orderId} "
. "a {$event->customerEmail} Total: {$event->total}n";
}
}
class StockReducerListener implements EventListener
{
private array $stock = [];
public function __construct(array $initialStock)
{
$this->stock = $initialStock;
}
public function listensTo(): EventType { return EventType::ItemAdded; }
public function handle(DomainEvent $event): void
{
/** @var ItemAddedEvent $event */
$name = $event->productName;
$this->stock[$name] = ($this->stock[$name] ?? 0) - $event->quantity;
echo "[STOCK] {$name}: stock reducido en {$event->quantity} ? disponible: {$this->stock[$name]}n";
}
public function getStock(string $product): int
{
return $this->stock[$product] ?? 0;
}
}
class AuditLogListener implements EventListener
{
private array $log = [];
public function listensTo(): EventType { return EventType::OrderPlaced; }
public function handle(DomainEvent $event): void
{
/** @var OrderPlacedEvent $event */
$entry = sprintf(
'[%s] Pedido %s %s cliente: %s',
$event->occurredAt->format('H:i:s'),
$event->orderId,
$event->total,
$event->customerEmail
);
$this->log[] = $entry;
echo "[AUDIT] $entryn";
}
public function getLogs(): array { return $this->log; }
}
class CartStatsListener implements EventListener
{
private int $itemsAdded = 0;
private float $totalValue = 0.0;
public function listensTo(): EventType { return EventType::ItemAdded; }
public function handle(DomainEvent $event): void
{
/** @var ItemAddedEvent $event */
$this->itemsAdded += $event->quantity;
$this->totalValue += $event->quantity * $event->unitPrice;
echo "[STATS] Carrito actualizado: {$this->itemsAdded} unidades, valor {$this->totalValue}n";
}
}
// ?????????????????????????????????????????????????????????????
// Demo
// ?????????????????????????????????????????????????????????????
echo "=== Observer Pattern Carrito de Compra ===nn";
$emitter = new EventEmitter();
// Registrar listeners
$emailListener = new EmailNotificationListener();
$stockListener = new StockReducerListener(['Teclado mecánico' => 50, 'Monitor 4K' => 20]);
$auditListener = new AuditLogListener();
$statsListener = new CartStatsListener();
$emitter->subscribe($emailListener);
$emitter->subscribe($stockListener);
$emitter->subscribe($auditListener);
$emitter->subscribe($statsListener);
// Simulación: añadir productos al carrito
echo "--- Añadiendo productos al carrito ---n";
$emitter->emit(new ItemAddedEvent('Teclado mecánico', 2, 89.99));
$emitter->emit(new ItemAddedEvent('Monitor 4K', 1, 349.00));
echo "n";
// Simulación: confirmar pedido
echo "--- Confirmando pedido ---n";
$emitter->emit(new OrderPlacedEvent('ORD-2026-001', 529.98, '[email protected]'));
echo "n";
// Dos observers distintos escuchan OrderPlaced (email + audit)
echo "Logs de auditoría guardados: " . count($auditListener->getLogs()) . "n";
echo "Stock restante de 'Teclado mecánico': " . $stockListener->getStock('Teclado mecánico') . "n";
Observer Pattern en PHP moderno con WeakReference
Implementación del patrón Observer en PHP 8.1 con interfaces, enums para tipos de evento y WeakReference para evitar memory leaks. Ejemplo práctico con carrito de compra: múltiples observadores reaccionan a ItemAdded, OrderPlaced y StockUpdated.
Descargar adjuntos
COMPARTE ESTE TUTORIAL
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP