miércoles, 25 de junio de 2025

Creación de Proxies Dinámicos con Reflection en PHP para Inyección de Dependencias AOP

Creación de Proxies Dinámicos con Reflection en PHP para Inyección de Dependencias AOP

Este artículo explora la creación de proxies dinámicos en PHP utilizando la API de Reflection para implementar un sistema básico de inyección de dependencias y Aspect-Oriented Programming (AOP). Esta técnica permite interceptar llamadas a métodos de una clase, ejecutar código adicional (advice) antes o después de la ejecución del método original, sin modificar la clase en sí.


<?php

// Interfaz para los aspectos (advice)
interface AspectInterface {
    public function before(string $methodName, array $arguments): void;
    public function after(string $methodName, array $arguments, mixed $result): void;
}

// Clase que queremos proxy
class MyService {
    public function doSomething(string $message): string {
        echo "Doing something with: " . $message . "\n";
        return "Result: " . $message;
    }
}

// Aspecto de logging
class LoggingAspect implements AspectInterface {
    public function before(string $methodName, array $arguments): void {
        echo "Before calling " . $methodName . " with arguments: " . json_encode($arguments) . "\n";
    }

    public function after(string $methodName, array $arguments, mixed $result): void {
        echo "After calling " . $methodName . ", result: " . $result . "\n";
    }
}

// Función para crear el proxy dinámicamente
function createProxy(object $target, AspectInterface $aspect): object {
    $className = get_class($target);
    $proxyClassName = 'Proxy_' . $className . '_' . uniqid();

    $reflectionClass = new ReflectionClass($className);
    $methods = $reflectionClass->getMethods();

    $proxyCode = "class {$proxyClassName} extends {$className} {\n";
    foreach ($methods as $method) {
        $methodName = $method->getName();
        $params = [];
        $paramNames = [];
        foreach ($method->getParameters() as $param) {
            $paramName = '$' . $param->getName();
            $params[] = ($param->hasType() ? ($param->getType() . ' ') : '') . $paramName . ($param->isDefaultValueAvailable() ? (' = ' . var_export($param->getDefaultValue(), true)) : '');
            $paramNames[] = $paramName;
        }

        $proxyCode .= "    public function {$methodName}(" . implode(', ', $params) . ") {\n";
        $proxyCode .= "        \$aspect->before('{$methodName}', [" . implode(', ', $paramNames) . "]);\n";
        $proxyCode .= "        \$result = parent::{$methodName}(" . implode(', ', $paramNames) . ");\n";
        $proxyCode .= "        \$aspect->after('{$methodName}', [" . implode(', ', $paramNames) . "], \$result);\n";
        $proxyCode .= "        return \$result;\n";
        $proxyCode .= "    }\n";
    }
    $proxyCode .= "}\n";

    eval($proxyCode); // ¡Precaución! Usar eval() puede ser peligroso.  Considere alternativas más seguras para producción.
    return new $proxyClassName();
}

// Uso
$service = new MyService();
$loggingAspect = new LoggingAspect();
$proxy = createProxy($service, $loggingAspect);

$result = $proxy->doSomething("Hello, World!");
echo "Final Result: " . $result . "\n";

?>
    

Este código define una interfaz `AspectInterface` que define los métodos `before` y `after`. Crea una clase `MyService` con un método `doSomething`. Un `LoggingAspect` implementa la interfaz `AspectInterface` para agregar logging antes y después de la ejecución del método. La función `createProxy` usa Reflection para inspeccionar la clase `MyService`, generar dinámicamente una subclase (`Proxy_MyService_*`), e interceptar las llamadas a los métodos de la clase original.


// Ejemplo de un Aspecto que modifica los argumentos

class ModifierAspect implements AspectInterface {
    public function before(string $methodName, array &$arguments): void {
        if ($methodName === 'doSomething') {
            $arguments[0] = 'Modified: ' . $arguments[0]; // Modifica el primer argumento
        }
    }

    public function after(string $methodName, array $arguments, mixed $result): void {
       //No hace nada en este ejemplo
    }
}

// Uso del Aspecto que modifica argumentos
$service = new MyService();
$modifierAspect = new ModifierAspect();
$proxy = createProxy($service, $modifierAspect);

$result = $proxy->doSomething("Original Message");
echo "Final Result (Modified): " . $result . "\n";
    

El ejemplo anterior muestra cómo un aspecto puede incluso modificar los argumentos pasados a un método antes de que se ejecute. Es crucial tener cuidado al modificar argumentos, ya que puede afectar el comportamiento esperado del método original. La función `createProxy` es un ejemplo simplificado y el uso de `eval()` puede ser un problema de seguridad. Para entornos de producción, considera usar bibliotecas de generación de código más seguras y robustas.

No hay comentarios:

Publicar un comentario