it-swarm.com.de

Klassenmethoden oder Klasse neu definieren

Gibt es eine Möglichkeit, eine Klasse oder einige ihrer Methoden ohne typische Vererbung neu zu definieren? Zum Beispiel:

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

Was kann ich tun, um buggy_function() zu ersetzen? Natürlich würde ich das gerne tun

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

Dies ist mein genaues Dilemma: Ich habe eine Bibliothek eines Drittanbieters aktualisiert, die meinen Code bricht. Ich möchte die Bibliothek nicht direkt ändern, da zukünftige Updates den Code erneut beschädigen könnten. Ich suche nach einer nahtlosen Möglichkeit, die Klassenmethode zu ersetzen. 

Ich habe diese Bibliothek gefunden, die besagt, dass sie es kann, aber ich bin vorsichtig, da sie 4 Jahre alt ist.

BEARBEITEN:

Ich hätte klarstellen müssen, dass ich die Klasse aufgrund von Rahmenbeschränkungen nicht von third_party_library in magical_third_party_library oder etwas anderem umbenennen kann.

Wäre es für meine Zwecke möglich, der Klasse nur eine Funktion hinzuzufügen? Ich denke, Sie können dies in C # mit etwas tun, das als "Teilklasse" bezeichnet wird.

42
SeanDowney

Es heißt Monkey Patching . Aber PHP hat keine native Unterstützung dafür.

Obwohl, wie andere bereits betont haben, die Runkit-Bibliothek verfügbar ist, um die Sprache zu unterstützen, ist sie der Nachfolger des Classkits . . Und obwohl es anscheinend von seinem Schöpfer aufgegeben wurde (nachdem festgestellt wurde, dass es nicht mit PHP 5.2 und höher kompatibel ist), funktioniert das Projekt jetzt scheinen ein neues Zuhause und einen neuen Betreuer zu haben .

Ich kann immer noch nicht sagen, dass ich ein Fan seiner Herangehensweise bin. Das Vornehmen von Änderungen durch Auswerten von Code-Zeichenfolgen erschien mir immer als potenziell gefährlich und schwierig zu debuggen.

Dennoch scheint runkit_method_redefine das zu sein, wonach Sie suchen, und ein Beispiel für dessen Verwendung finden Sie in /tests/runkit_method_redefine.phpt in der Repository:

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

runkit scheint eine gute Lösung zu sein, aber standardmäßig nicht aktiviert und Teile davon sind noch experimentell. Also habe ich eine kleine Klasse gehackt, die Funktionsdefinitionen in einer Klassendatei ersetzt. Verwendungsbeispiel:

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

Fügen Sie dann die zu ändernde Klasse ein und definieren Sie ihre Methoden neu:

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

Dann den neuen Code bewerten:

eval($objPatch->getCode());

Ein bisschen grob aber es funktioniert!

14
JPhilly

Der Vollständigkeit halber ist das Affen-Patching in PHP über runkit verfügbar. Einzelheiten finden Sie unter runkit_method_redefine().

11
Till

Ja, es heißt extend:

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

Ich habe "sd" vorangestellt. ;-)

Wenn Sie eine Klasse erweitern, um Methoden zu überschreiben, muss die Signatur der Methode mit dem Original übereinstimmen. Wenn das Original beispielsweise buggy_function($foo, $bar) sagt, muss es mit den Parametern in der Klasse übereinstimmen, die es erweitert.

PHP ist ziemlich wortreich darüber.

8
Till

Wie wäre es, wenn Sie es in eine andere Klasse packen?

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);
 }
}
8
Newbie

Für Leute, die noch nach dieser Antwort suchen.

Sie sollten extend in Kombination mit Namespaces verwenden.

so was:

namespace MyCustomName;

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

Dann verwenden Sie es wie folgt:

use MyCustomName\third_party_library;

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

Zend Studio und PDT (Eclipse-basierte Ide) verfügen über einige eingebaute Refraktionswerkzeuge. Dafür gibt es keine integrierten Methoden.

Sie möchten auch keinen schlechten Code in Ihrem System haben. Da könnte man aus Versehen angerufen werden.

1
Ólafur Waage

Es gibt immer eine Erweiterung der Klasse mit einer neuen, richtigen Methode, die statt der fehlerhaften Klasse aufgerufen wird.

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

(Sorry für die beschissene Formatierung)

0
Eric Lamb

Wenn die Bibliothek die fehlerhafte Klasse explizit erstellt und keinen Locater oder ein Abhängigkeitssystem verwendet, haben Sie kein Glück. Es ist nicht möglich, eine Methode in einer anderen Klasse außer Kraft zu setzen, es sei denn, Sie verwenden die Unterklasse . Die Lösung besteht darin, eine Patch-Datei zu erstellen, die die Bibliothek repariert, sodass Sie die Bibliothek aktualisieren und den Patch erneut anwenden können, um diese bestimmte Methode zu beheben.

0
MOdMac

Möglicherweise können Sie dies mit Runkit tun. http://php.net/runkit

0
Toxygene

Sie können eine Kopie der Bibliotheksklasse erstellen, wobei alles außer dem Klassennamen identisch ist. Dann überschreiben Sie die umbenannte Klasse.

Es ist nicht perfekt, verbessert jedoch die Sichtbarkeit der Änderungen der Erweiterungsklasse. Wenn Sie die Bibliothek mit etwas wie Composer abrufen, müssen Sie die Kopie an die Quellcodeverwaltung übergeben und sie aktualisieren, wenn Sie die Bibliothek aktualisieren.

In meinem Fall war es eine alte Version von https://github.com/bshaffer/oauth2-server-php . Ich habe den Autoloader der Bibliothek geändert, um meine Klassendatei abzurufen. Meine Klassendatei nahm den ursprünglichen Namen an und erweiterte eine kopierte Version einer der Dateien.

0
Ben Creasy

Da Sie in PHP immer auf den Basiscode zugreifen können, müssen Sie die wichtigsten Klassenfunktionen, die Sie überschreiben möchten, wie folgt neu definieren. Dadurch sollten Ihre Schnittstellen intakt bleiben:

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

Sie können aus irgendeinem Grund eine private Variable verwenden, aber Sie können nur auf die Funktion zugreifen, indem Sie die Klasse oder Logik innerhalb der Klasse erweitern. Ebenso ist es möglich, dass verschiedene Objekte derselben Klasse unterschiedliche Funktionen haben sollen. Verwenden Sie in diesem Fall statisch nicht. Normalerweise möchten Sie jedoch, dass es statisch ist, sodass Sie den Speicher für jedes erstellte Objekt nicht duplizieren. Der 'ranOnce'-Code sorgt lediglich dafür, dass Sie ihn nur einmal für die Klasse initialisieren müssen, nicht für jede $myObject = new third_party_library().

Später in Ihrem Code oder einer anderen Klasse - wann immer die Logik einen Punkt trifft, an dem Sie die Funktion überschreiben müssen - gehen Sie einfach wie folgt vor:

$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'];

Als Randbemerkung: Wenn Sie alle Klassenfunktionen auf diese Weise ausführen und einen zeichenfolgenbasierten Schlüssel-/Wertspeicher wie public static $functions['function_name'] = function(...){...}; verwenden, kann dies für die Reflektion hilfreich sein. Nicht so sehr in PHP wie in anderen Sprachen, da Sie bereits die Klassen- und Funktionsnamen abrufen können, Sie können jedoch einige Verarbeitungsschritte sparen und zukünftige Benutzer Ihrer Klasse können Überschreibungen in PHP verwenden. Es ist jedoch eine zusätzliche Ebene der Dereferenzierung, daher würde ich es, wo immer möglich, auf primitive Klassen verwenden.

0
Garet Claborn