2011-02-06 16 views
6

Me gustaría tener una clase contenedora genérica para algunas clases para interceptar y manipular algunas de las llamadas a métodos. Método de desvío de llamadas, interceptación, no hay problema hasta el momento. Pero después de pensarlo un tiempo, encontré un problema para el cual no tengo solución: estoy usando el operador-instancia incorporado en todas partes de mi aplicación. Por supuesto, esto ya no funcionará, porque el contenedor no es una instancia de la clase dentro de él. Me gustaría continuar usando el operador y no reemplazarlo con otra función.Manipular PHP-instanceof-operator para wrapper-class

¿Hay alguna manera de implementar una solución para este problema? ¿Cómo funciona este operador? ¿Llama a una función central de las clases que probablemente pueda sobrescribir en mi envoltorio?

Sé que esta no sería una solución realmente "limpia" para manipular este operador, pero creo que esta sería la solución más simple para mí. Y como sabemos, hay muchas cosas en PHP que no son tan limpia ... :-)

Gracias por sus respuestas, Ben

+0

Este es exactamente el mismo problema que trato de abordar. ¿Logramos que esto funcione de alguna manera? Si es así, me interesaría mucho cómo lo hiciste. – mrjames

Respuesta

0

utilizar una interfaz en lugar de la clase concreta. Aplicar la interfaz a Wrapper y Concrete Class.

Ver http://de3.php.net/manual/en/language.oop5.interfaces.php

+0

Me gustaría tener un contenedor genérico para múltiples clases que no implementen la misma interfaz y que no tengan las mismas clases. Entonces, con tu solución, necesito una clase contenedora para cada clase diferente. ¿O estoy equivocado? – Ben

+0

@Ben tienes razón. Esa es la desventaja. – Gordon

2

No sé ¿Es posible engañar a un operador instanceof en la forma que desee (reconocer una clase como subclase si no lo es), pero creo que he encontrado una solución que pueda satisfacer sus necesidades . Si entiendo correctamente tu problema, simplemente quieres inyectar algunos métodos en cualquier clase con cambios mínimos en todo tu código.

Creo que la mejor manera de preparar una solución en este caso es usando los rasgos (descritos here). Con los rasgos puede agregar métodos a cualquier clase sin herencia directa y puede sobreescribir los métodos de la clase base. Para sobrescribir el método con rasgos, por supuesto, necesitas una subclase, pero se pueden crear dinámicamente. No sé nada acerca de su proceso de envoltura pero en mi solución usé una clase especial para ello. Vamos a mirar a mi solución:

namespace someNameSpace; 

//this is one of your class that you want to wrap - it can be declare under some other namespace if you need 
class yourBaseClass { } 

//your wrapper class as a trait 
trait yourWrapper { } 

//class for wrapping any object 
class ObjectWrapperClass 
{ 
    //method for change object class (described on http://stackoverflow.com/a/3243949/4662836) 
    protected static function objectToObject($instance, $className) 
    { 
     return unserialize(sprintf('O:%d:"%s"%s', strlen($className), $className, strstr(strstr(serialize($instance), '"'), ':'))); 
    } 

    //wrapping method 
    //$object is a object to be wrapped 
    //$wrapper is a full name of the wrapper trait 
    public static function wrap($object, $wrapper) 
    { 
     //take some information about the object to be wrapped 
     $reflection = new \ReflectionClass($object); 
     $baseClass = $reflection->getShortName(); 
     $namespace = $reflection->getNamespaceName(); 

     //perpare the name of the new wrapped class 
     $newClassName = "{$baseClass}Wrapped"; 

     //if new wrapped class has not been declared before we need to do it now 
     if (!class_exists($newClassName)) { 
      //prepare a code of the wrapping class that inject trait 
      $newClassCode = "namespace {$namespace} { class {$newClassName} extends {$baseClass} { use {$wrapper}; } }"; 

      //run the prepared code 
      eval($newClassCode); 
     } 

     //change the object class and return it 
     return self::objectToObject($object, $namespace . '\\' . $newClassName); 
    } 

} 

//lets test this solution 

$originalObject = new yourBaseClass(); 

$wrappedObject = ObjectWrapperClass::wrap($originalObject, 'yourWrapper'); 

if ($wrappedObject instanceof yourBaseClass) { 
    echo 'It is working'; 
} 

Como se puede ver todo lo que se pasa durante el proceso de envoltura.

Si tiene más envoltorios, entonces puede preparar el nuevo nombre de clase envuelto de otra manera (por ejemplo, para ser corelated con el nombre de envoltura).

+0

Esa es una solución inteligente, pero no quiero usar el reflejo ni la evaluación porque son demasiado lentos y se usarían mucho para mi caso de uso. Tal vez resuelva el problema con la generación de código y haciendo un contenedor para cada clase que necesito envolver, si las envolturas se extienden desde las clases envueltas entonces mi problema está resuelto, pero realmente no me gusta. Ojalá hubiera un método mágico __instanceOf para resolver este problema. Gracias de todos modos. – hchinchilla

0

Eche un vistazo a decorator pattern. Si sus clases wrapper/wrapped implementan la misma interfaz, puede hacerlo todo elegantemente (y utilice la instancia de en todo el código).

¿Hay alguna manera de implementar una solución para este problema? ¿Cómo funciona este operador? ¿Llama a una función central de las clases que probablemente pueda sobrescribir en mi envoltorio?

No se puede manipular el operador de instanceof. Dado que usted estaba interesado cómo operador instanceof se implementa, aquí es una representación de PHP de código original C:

class php_class { 
    public $interfaces = array(); // array of php_class objects (php classes can implement more than one interface) 
    public $parent = null; // php_class object (php classes can only extend one class) 
} 

function instanceof_operator($implementation, $abstraction) { 
    // forward recursion (iterates recursively through interfaces until a match is found) 
    for($i=0; $i<count($implementation->interfaces); $i++) { 
     if(instanceof_operator($implementation->interfaces[$i], $abstraction)) { 
      return true; 
     } 
    } 
    // backward recursion (iterates recursively through parents until a match is found) 
    while($implementation!=null) { 
     if($implementation == $abstraction) { 
      return true; 
     } 
     $implementation = $implementation->parent; 
    } 
    // no match was found 
    return false; 
} 

Cuando se declara una clase para implementar/ampliar una interfaz/clase, imagina una entrada se deposita en $ interfaces o $ campos parentales que permanecen inmutables hasta que termina la secuencia de comandos.

2

Probablemente pueda describir una solución para sus necesidades. (Descargo de responsabilidad: soy autor de Go! AOP Framework) A partir de su descripción, parece que desea agregar dinámicamente una lógica adicional a sus métodos sin tocar la clase. Si estoy en lo cierto, entonces podría echar un vistazo a Aspect-Oriented Paradigm que introduce un concepto de interceptores para su código fuente, lo que es más importante: sus clases originales quedarán intactas.

Para tener una idea, cómo esto se puede aplicar a su código, también puede echar un vistazo a mi artículo http://go.aopphp.com/blog/2014/10/19/caching-like-a-pro/ que destaca todas las ventajas y desventajas de los patrones clásicos orientados a objetos como decorador, proxy. Puedo llegar a la conclusión de que no se pueden extraer todos los interceptores en módulos separados en forma orientada a objetos debido a la complejidad esencial y las limitaciones de PHP para resolver problemas transversales. AOP amplía el modelo OOP tradicional, por lo que será posible extraer interceptores (llamados consejos) en clases separadas (llamadas aspectos).

La característica brillante de AOP es que mantiene sus nombres de clase originales y esto significa que no debe cambiar los tipos en su código o incluso secuestrar un operador instanceof. Obtendrás tu clase con lógica adicional.

Cuestiones relacionadas