2008-09-26 19 views
37

¿Hay alguna forma de redefinir una clase o algunos de sus métodos sin usar herencia típica? Por ejemplo:Redefina los métodos de clase o clase

class third_party_library { 
    function buggy_function() { 
     return 'bad result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

¿Qué puedo hacer para reemplazar buggy_function()? Obviamente, esto es lo que me gustaría hacer

class third_party_library redefines third_party_library{ 
    function buggy_function() { 
     return 'good result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

Ésta es mi dilema exacta: Me ha modificado una biblioteca de terceros que rompe mi código. No quiero modificar la biblioteca directamente, ya que las futuras actualizaciones podrían volver a romper el código. Estoy buscando una manera perfecta de reemplazar el método de clase.

He encontrado este library que dice que puede hacerlo, pero soy cauteloso ya que tiene 4 años.

EDIT:

debería haber aclarado que no puedo cambiar el nombre de la clase de third_party_library a magical_third_party_library o cualquier otra cosa debido a las limitaciones del marco.

Para mis propósitos, ¿sería posible simplemente agregar una función a la clase? Creo que puedes hacer esto en C# con algo llamado "clase parcial".

+0

PHP no es compatible con eso. Puede extender la clase y volver a usarla. Eso es. Lo siento. – Till

+0

¿Puedes cambiar el nombre de la clase original con errores? Cambie el nombre a buggy_third_party y ejecútelo usted mismo, dando a su clase el nombre original. – gnud

Respuesta

36

Se llama monkey patching. Pero, PHP no tiene soporte nativo para ello.

Aunque, como otros también han señalado, el runkit library está disponible para agregar soporte al idioma y es el sucesor del classkit. Y, aunque parecía haber sido abandoned por su creador (habiendo declarado que no era compatible con PHP 5.2 y posterior), ahora el proyecto parece tener un new home and maintainer.

Sigo can't say I'm a fan de su enfoque. Realizar modificaciones mediante la evaluación de cadenas de código siempre me ha parecido potencialmente peligroso y difícil de depurar.

embargo, runkit_method_redefine parece ser lo que está buscando, y un ejemplo de su uso se pueden encontrar en /tests/runkit_method_redefine.phpt en el repositorio:

runkit_method_redefine('third_party_library', 'buggy_function', '', 
    'return \'good result\'' 
); 
+0

esto fue aceptado como la respuesta: ¿eso significa que PHP lo admite? algo más de información sería agradable. – nickf

+0

@nickf: "No creo" es una negación; por lo que OP significa "no admitido en PHP". Por lo que puedo decir, ** PHP no admite el parche de mono **. – Piskvor

+0

@Piskvor, bueno, sí, puedo leer, pero era curioso que esta respuesta fuera aceptada sobre las otras que ofrecían alguna forma de orientación. – nickf

1

Zend Studio y PDT (eclipse basado en ide) tienen algunas herramientas de refracción integradas. Pero no hay métodos integrados para hacer esto.

Además, no querrá tener un código incorrecto en su sistema. Dado que podría ser invocado por error.

9

Sí, se llama extend:

<?php 
class sd_third_party_library extends third_party_library 
{ 
    function buggy_function() { 
     return 'good result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

I prefijo con "sd". ;-)

Tenga en cuenta que cuando amplía una clase para anular los métodos, la firma del método debe coincidir con la original. Entonces, por ejemplo, si el original dijo buggy_function($foo, $bar), tiene que coincidir con los parámetros en la clase que lo extiende.

PHP es bastante detallado al respecto.

+2

Eso normalmente funcionaría, sin embargo, no puedo cambiar el nombre de la clase debido a la forma en que el framework que estoy usando es configuración – SeanDowney

+4

Entonces la respuesta es, no se puede. – Till

+0

¿Por qué estoy bajando la votación? =) – Till

2

Siempre se está ampliando la clase con un método nuevo y adecuado y se está llamando a esa clase en lugar de a la incorrecta.

class my_better_class Extends some_buggy_class { 
    function non_buggy_function() { 
     return 'good result'; 
    } 
} 

(Lo siento por el formato de mierda)

+0

¿Leyó la pregunta? Tenía bastante claro que no podía usar esta solución. –

+1

no importa - me ayuda. : P –

0

Si la biblioteca está creando explícitamente la mala clase y no se utiliza un sistema de localizador o dependencia que están fuera de suerte. No hay forma de reemplazar un método en otra clase a menos que tenga una subclase. La solución podría ser crear un archivo de parche que corrige la biblioteca, para que pueda actualizar la biblioteca y volver a aplicar el parche para corregir ese método específico.

10

Para completar, el parche de mono está disponible en PHP a través de runkit. Para más detalles, vea runkit_method_redefine().

+1

¡gracias por agregar una respuesta completa a esta pregunta! – nickf

8

¿Qué hay de envolverlo en otra clase como

class Wrapper { 
private $third_party_library; 
function __construct() { $this->third_party_library = new Third_party_library(); } 
function __call($method, $args) { 
    return call_user_func_array(array($this->third_party_library, $method), $args); 
} 
} 
+0

Esto es perfecto para envolver una clase importante. también puede pasar la variable php original al constructor y cambiarla por $ this. (útil para una clase principal, no tan útil para muchas instancias) – GDmac

14

runkit parece una buena solución, pero no es activada por defecto y partes de él son todavía experimentales. Así que arreglé una pequeña clase que reemplaza las definiciones de funciones en un archivo de clase. Ejemplo de uso:

class Patch { 

private $_code; 

public function __construct($include_file = null) { 
    if ($include_file) { 
     $this->includeCode($include_file); 
    } 
} 

public function setCode($code) { 
    $this->_code = $code; 
} 

public function includeCode($path) { 

    $fp = fopen($path,'r'); 
    $contents = fread($fp, filesize($path)); 
    $contents = str_replace('<?php','',$contents); 
    $contents = str_replace('?>','',$contents); 
    fclose($fp);   

    $this->setCode($contents); 
} 

function redefineFunction($new_function) { 

    preg_match('/function (.+)\(/', $new_function, $aryMatches); 
    $func_name = trim($aryMatches[1]); 

    if (preg_match('/((private|protected|public) function '.$func_name.'[\w\W\n]+?)(private|protected|public)/s', $this->_code, $aryMatches)) { 

     $search_code = $aryMatches[1]; 

     $new_code = str_replace($search_code, $new_function."\n\n", $this->_code); 

     $this->setCode($new_code); 

     return true; 

    } else { 

     return false; 

    } 

} 

function getCode() { 
    return $this->_code; 
} 
} 

Luego incluyen la clase que se desea modificar y redefinir sus métodos:

$objPatch = new Patch('path_to_class_file.php'); 
$objPatch->redefineFunction(" 
    protected function foo(\$arg1, \$arg2) 
    { 
     return \$arg1+\$arg2; 
    }"); 

Entonces Eval el nuevo código:

eval($objPatch->getCode()); 

Un poco crudo, pero funciona!

+0

¡eso es un viaje! la instrucción eval luego define la clase con la función reemplazada? entonces, ¿todo lo que necesito hacer es crear una instancia del objeto y ejecutarlo? – SeanDowney

+1

Yessir! Eso es todo lo que necesitas hacer. – JPhilly

+0

@SeanDowney, esto casi parece ser cierto. ¿Esta clase funciona en la caja? ¿Obtuviste los resultados esperados @SeanDowney? Haría que mi vida sea más fácil. – Theunis

0

Puede hacer una copia de la clase de la biblioteca, con todo igual excepto el nombre de la clase. Luego, anule esa clase renombrada.

No es perfecto, pero mejora la visibilidad de los cambios de la clase que se extiende. Si busca la biblioteca con algo así como Composer, deberá enviar la copia al control de fuente y actualizarla cuando actualice la biblioteca.

En mi caso, era una versión anterior de https://github.com/bshaffer/oauth2-server-php. Modifiqué el autocargador de la biblioteca para buscar el archivo de mi clase. Mi archivo de clase tomó el nombre original y extendió una versión copiada de uno de los archivos.

2

Para las personas que todavía están buscando esta respuesta.

Debe usar extends en combinación con namespaces.

así:

namespace MyCustomName; 

class third_party_library extends \third_party_library { 
    function buggy_function() { 
     return 'good result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

Luego de usar que haga de esta manera:

use MyCustomName\third_party_library; 

$test = new third_party_library(); 
$test->buggy_function(); 
//or static. 
third_party_library::other_functions(); 
+0

que es una solución interesante. el único inconveniente que veo es cuando necesitas cambiar el comportamiento de la clase original en los lugares donde se usa. –

0

Ya que siempre tiene acceso a la base de código en PHP, redefinir las principales funciones de la clase que desea anular de la siguiente manera, esto debería dejar intactas sus interfaces:

class third_party_library { 
    public static $buggy_function; 
    public static $ranOnce=false; 

    public function __construct(){ 
     if(!self::$ranOnce){ 
      self::$buggy_function = function(){ return 'bad result'; }; 
      self::$ranOnce=true; 
     } 
     . 
     . 
     . 
    } 
    function buggy_function() { 
     return self::$buggy_function(); 
    }   
} 

Puede que por alguna razón use un priva la variable pero luego solo podrá acceder a la función al extender la clase o la lógica dentro de la clase. Del mismo modo, es posible que desee tener diferentes objetos de la misma clase con diferentes funciones. Si es así, no use estática, pero generalmente quiere que sea estática para que no duplique el uso de la memoria para cada objeto creado. El código 'ranOnce' simplemente se asegura de que solo necesita inicializarlo una vez para la clase, no para cada $myObject = new third_party_library()

Ahora, más adelante en su código u otra clase, siempre que la lógica llegue a un punto donde necesita anular el función - simplemente hacer de la siguiente manera:

$backup['buggy_function'] = third_party_library::$buggy_function; 
third_party_library::$buggy_function = function(){ 
    //do stuff 
    return $great_calculation; 
} 
. 
. 
. //do other stuff that needs the override 
. //when finished, restore the original function 
. 
third_party_library::$buggy_function=$backup['buggy_function']; 

como nota al margen, si lo hace todas sus funciones de clase de esta manera y utiliza un almacén de claves/valor basado en cadena como public static $functions['function_name'] = function(...){...}; esto puede ser útil para la reflexión. No tanto en PHP como en otros lenguajes, porque ya puedes tomar los nombres de clases y funciones, pero puedes guardar algo de procesamiento y los futuros usuarios de tu clase pueden usar anulaciones en PHP. Sin embargo, es un nivel adicional de indirección, por lo que evitaría usarlo en clases primitivas siempre que sea posible.

Cuestiones relacionadas