Pregunta

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

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';
    }
}

Este es mi dilema exacto: actualicé una biblioteca de terceros que rompe mi código. No quiero modificar la biblioteca directamente, ya que futuras actualizaciones podrían volver a romper el código. Estoy buscando una manera perfecta para reemplazar el método de clase.

He encontrado esta biblioteca que dice que puede hacerlo, pero desconfío ya que tiene 4 años.

EDITAR:

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 que se llama una "clase parcial". "

¿Fue útil?

Solución

Se llama parches de mono . Pero, PHP no tiene soporte nativo para ello.

Aunque, como otros también han señalado, la biblioteca de runkit está disponible para agregar soporte al idioma y es el sucesor de classkit . Y, aunque parecía haber sido abandonado por su creador (habiendo declarado que no era compatible con PHP 5.2 y versiones posteriores), el proyecto ahora parece tener un nuevo hogar y mantenedor .

Todavía no puedo decir que soy un 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.

Aún así, runkit_method_redefine parece ser lo que estás buscando, y puedes encontrar un ejemplo de su uso en /tests/runkit_method_redefine.phpt en el repositorio:

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

Otros consejos

El

runkit parece una buena solución, pero no está habilitado de forma predeterminada y algunas de sus partes aún son experimentales. Así que hackeé una pequeña clase que reemplaza las definiciones de funciones en un archivo de clase. Ejemplo de uso:

class Patch {

private El 

runkit parece una buena solución, pero no está habilitado de forma predeterminada y algunas de sus partes aún son experimentales. Así que hackeé una pequeña clase que reemplaza las definiciones de funciones en un archivo de clase. Ejemplo de uso:

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

Luego incluya la clase que se modificará y redefina sus métodos:

eval($objPatch->getCode());

Luego evalúa el nuevo código:

<*>

¡Un poco crudo pero funciona!

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 incluya la clase que se modificará y redefina sus métodos:

<*>

Luego evalúa el nuevo código:

<*>

¡Un poco crudo pero funciona!

En aras de la integridad, el parche de mono está disponible en PHP a través de runkit . Para obtener más información, consulte runkit_method_redefine () .

¿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);
 }
}

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';
    }
}

Prefijo con " sd " ;. ;-)

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

PHP es bastante detallado al respecto.

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

Debería usar extiende en combinación con espacios de nombres .

como este:

namespace MyCustomName;

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

Luego, para usarlo, haz esto:

use MyCustomName\third_party_library;

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

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

Además, no querrá tener ningún código incorrecto en su sistema. Ya que podría ser invocado por error.

Si la biblioteca está creando explícitamente la clase errónea y no está utilizando un sistema de localización o dependencia, está fuera de suerte. No hay manera de anular un método en otra clase a menos que usted subclase. La solución podría ser crear un archivo de parche que corrija la biblioteca, para que pueda actualizar la biblioteca y volver a aplicar el parche para corregir ese método específico.

Es posible que puedas hacer esto con el kit de ejecución. http://php.net/runkit

Siempre se está extendiendo la clase con un método nuevo y adecuado y llamando a esa clase en lugar del buggy.

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

(Perdón por el formato de mierda)

Puedes hacer una copia de la clase de la biblioteca, con todo lo mismo excepto el nombre de la clase. Luego anula la clase cuyo nombre ha cambiado.

No es perfecto, pero mejora la visibilidad de los cambios en la clase. Si obtiene la biblioteca con algo como Composer, tendrá que enviar la copia al control de origen y actualizarla cuando actualice la biblioteca.

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

Como siempre tiene acceso al código base en PHP, vuelva a definir las funciones de clase principales que desea reemplazar de la siguiente manera, esto debería dejar sus interfaces intactas:

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();
    }        
}

Por alguna razón, puede usar una variable privada, pero solo podrá acceder a la función extendiendo la clase o la lógica dentro de la clase. Del mismo modo, es posible que desee que los diferentes objetos de la misma clase tengan diferentes funciones. Si es así, no use la estática, pero normalmente desea que sea estática para no duplicar el uso de memoria para cada objeto creado. El código 'ranOnce' solo asegura que solo necesitas inicializarlo una vez para la clase, no para cada $ myObject = new third_party_library ()

Ahora, más adelante en su código o en otra clase, siempre que la lógica llegue a un punto en el que deba anular la función, simplemente haga lo siguiente:

$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 adicional, si realiza todas las funciones de su clase de esta manera y utiliza un almacén de clave / valor basado en cadenas como $ static static static ['function_name'] = function (...) {.. .}; esto puede ser útil para la reflexión. Sin embargo, no tanto en PHP como en otros idiomas porque ya puedes tomar los nombres de clase y función, 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top