miércoles, 25 de junio de 2025

Implementando Observadores Personalizados con Reflection en PHP

Implementando Observadores Personalizados con Reflection en PHP

En este post, exploraremos una técnica avanzada para implementar un sistema de observadores (Observer pattern) altamente configurable y dinámico en PHP, utilizando Reflection para descubrir y registrar métodos observadores automáticamente. Esto permite una mayor flexibilidad y reduce la necesidad de configuración manual extensa.

Normalmente, el patrón Observer implica suscribir objetos a un sujeto (Subject) y, cuando el estado del sujeto cambia, notificar a todos los observadores. Este enfoque va un paso más allá, permitiendo que los observadores definan múltiples métodos observadores y que el sujeto los invoque dinámicamente basándose en un "evento" o "notificación" específico.


<?php

interface ObserverInterface {
    // Marcador para identificar Observadores
}

class Subject {
    private array $observers = [];

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

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

    public function notify(string $event, mixed $data = null): void
    {
        foreach ($this->observers as $observer) {
            $reflection = new ReflectionClass($observer);
            $methodName = 'on' . ucfirst($event); // Ejemplo: onUserCreated, onOrderShipped

            if ($reflection->hasMethod($methodName)) {
                $method = $reflection->getMethod($methodName);
                $method->invoke($observer, $data); // Invocamos el método con la data
            }
        }
    }
}

// Ejemplo de Observer
class UserObserver implements ObserverInterface {
    public function onUserCreated(array $userData): void
    {
        echo "Nuevo usuario creado: " . $userData['email'] . "\n";
    }

    public function onUserDeleted(array $userData): void
    {
        echo "Usuario eliminado: " . $userData['email'] . "\n";
    }
}

// Ejemplo de uso
$subject = new Subject();
$userObserver = new UserObserver();
$subject->attach($userObserver);

$userData = ['email' => 'test@example.com', 'name' => 'Test User'];
$subject->notify('UserCreated', $userData);
$subject->notify('UserDeleted', $userData);

?>
    

El código anterior muestra una implementación básica. La clave está en el método notify. Utiliza ReflectionClass para inspeccionar cada observador adjunto. Construye el nombre del método a llamar basándose en el evento notificado (por ejemplo, transforma "UserCreated" en "onUserCreated"). Luego, verifica si el observador tiene un método con ese nombre. Si existe, lo invoca utilizando $method->invoke($observer, $data), pasando la información relevante ($data) al método observador.

Este enfoque permite una gran extensibilidad. Simplemente implemente la interfaz ObserverInterface, defina los métodos observadores (por ejemplo, onProductUpdated, onOrderCancelled) y adjunte el observador al sujeto. No es necesario modificar la lógica del sujeto para cada nuevo tipo de evento o observador.


// Ejemplo de otro Observer que reacciona a diferentes eventos.
class OrderObserver implements ObserverInterface {
    public function onOrderCreated(array $orderData): void {
        echo "Nuevo pedido creado con ID: " . $orderData['id'] . "\n";
    }

    public function onOrderShipped(array $orderData): void {
        echo "Pedido enviado con ID: " . $orderData['id'] . "\n";
    }
}

$orderObserver = new OrderObserver();
$subject->attach($orderObserver);

$orderData = ['id' => 123, 'total' => 99.99];
$subject->notify('OrderCreated', $orderData);
$subject->notify('OrderShipped', $orderData);

    

Al usar Reflection, logramos un sistema de observadores mucho más dinámico y adaptable. Podemos agregar nuevos eventos y observadores sin modificar la lógica central del sujeto, lo que mejora la mantenibilidad y la escalabilidad de la aplicación. Recuerde considerar el impacto en el rendimiento de usar Reflection y optimice cuando sea necesario, posiblemente usando caché para los metadatos reflejados.

No hay comentarios:

Publicar un comentario