Domanda

Esiste un modo per ridefinire una classe o alcuni dei suoi metodi senza utilizzare l'ereditarietà tipica? Ad esempio:

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

Cosa posso fare per sostituire buggy_function () ? Ovviamente questo è quello che vorrei fare

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

Questo è il mio dilemma esatto: ho aggiornato una libreria di terze parti che rompe il mio codice. Non voglio modificare direttamente la libreria, poiché i futuri aggiornamenti potrebbero rompere nuovamente il codice. Sto cercando un modo semplice per sostituire il metodo di classe.

Ho trovato questa libreria che dice che può farlo, ma sono diffidente dato che ha 4 anni.

EDIT:

Avrei dovuto chiarire che non posso rinominare la classe da third_party_library a magical_third_party_library o qualsiasi altra cosa a causa delle limitazioni del framework.

Ai miei scopi, sarebbe possibile aggiungere una funzione alla classe? Penso che tu possa farlo in C # con qualcosa chiamato "classe parziale". & Quot;

È stato utile?

Soluzione

Si chiama patching delle scimmie . Ma PHP non ha il supporto nativo per esso.

Sebbene, come altri hanno anche sottolineato, la libreria runkit è disponibile per aggiungere supporto alla lingua ed è il successore di classkit . E, sebbene sembrasse essere stato abbandonato dal suo creatore (avendo dichiarato che non era compatibile con PHP 5.2 e versioni successive), il progetto sembra ora avere una nuova casa e manutentore .

Continuo a non posso dire di essere un fan del suo approccio. Apportare modifiche valutando stringhe di codice mi è sempre sembrato potenzialmente pericoloso e difficile da eseguire il debug.

Ancora, runkit_method_redefine sembra essere quello che stai cercando, e un esempio del suo uso può essere trovato in /tests/runkit_method_redefine.phpt nel repository:

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

Altri suggerimenti

runkit sembra una buona soluzione ma non è abilitato di default e alcune parti sono ancora sperimentali. Così ho hackerato insieme una piccola classe che sostituisce le definizioni delle funzioni in un file di classe. Esempio di utilizzo:

class Patch {

private 

runkit sembra una buona soluzione ma non è abilitato di default e alcune parti sono ancora sperimentali. Così ho hackerato insieme una piccola classe che sostituisce le definizioni delle funzioni in un file di classe. Esempio di utilizzo:

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

Quindi includere la classe da modificare e ridefinire i suoi metodi:

eval($objPatch->getCode());

Quindi valuta il nuovo codice:

<*>

Un po 'rozzo ma funziona!

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

Quindi includere la classe da modificare e ridefinire i suoi metodi:

<*>

Quindi valuta il nuovo codice:

<*>

Un po 'rozzo ma funziona!

Per completezza: il patching delle scimmie è disponibile in PHP tramite runkit . Per i dettagli, vedere runkit_method_redefine () .

Che ne dici di racchiuderlo in un'altra classe come

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ì, si chiama estende :

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

Ho aggiunto il prefisso "quot" sd ". ; -)

Tieni presente che quando estendi una classe per sovrascrivere i metodi, la firma del metodo deve corrispondere all'originale. Quindi, ad esempio, se l'originale diceva buggy_function ($ foo, $ bar) , deve corrispondere ai parametri nella classe che lo estende.

PHP è piuttosto prolisso al riguardo.

Per le persone che stanno ancora cercando questa risposta.

Dovresti usare extends in combinazione con spazi dei nomi .

in questo modo:

namespace MyCustomName;

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

Quindi per usarlo fai così:

use MyCustomName\third_party_library;

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

Zend Studio e PDT (ide basato su eclipse) hanno alcuni strumenti di rifrazione integrati. Ma non ci sono metodi integrati per farlo.

Inoltre non dovresti avere affatto un codice errato nel tuo sistema. Dal momento che potrebbe essere chiamato per errore.

Se la libreria crea esplicitamente la classe errata e non utilizza un sistema di localizzazione o dipendenza, si è sfortunati. Non è possibile sovrascrivere un metodo su un'altra classe se non si esegue la sottoclasse. La soluzione potrebbe essere quella di creare un file patch che ripari la libreria, quindi puoi aggiornare la libreria e riapplicare la patch per correggere quel metodo specifico.

Potresti riuscire a farlo con runkit. http://php.net/runkit

Esiste sempre l'estensione della classe con un nuovo metodo appropriato e la chiamata di quella classe invece di quella buggy.

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

(Ci scusiamo per la formattazione scadente)

Puoi fare una copia della classe della libreria, con tutto uguale a eccezione del nome della classe. Quindi sovrascrivere quella classe rinominata.

Non è perfetto, ma migliora la visibilità delle modifiche della classe in estensione. Se recuperi la libreria con qualcosa come Composer, dovrai affidare la copia al controllo del codice sorgente e aggiornarla quando aggiorni la libreria.

Nel mio caso era una vecchia versione di https://github.com/bshaffer/ OAuth2-server-php . Ho modificato il caricatore automatico della libreria per recuperare invece il mio file di classe. Il mio file di classe ha assunto il nome originale ed esteso una versione copiata di uno dei file.

Dato che hai sempre accesso al codice di base in PHP, ridefinisci le principali funzioni di classe che vuoi sovrascrivere come segue, questo dovrebbe lasciare intatte le tue interfacce:

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

Per qualche motivo potresti usare una variabile privata ma potrai accedere alla funzione solo estendendo la classe o la logica all'interno della classe. Allo stesso modo è possibile che si desideri che oggetti diversi della stessa classe abbiano funzioni diverse. In tal caso, non utilizzare statico, ma di solito si desidera che sia statico in modo da non duplicare l'uso della memoria per ogni oggetto creato. Il codice 'ranOnce' si assicura solo che sia necessario inizializzarlo solo una volta per la classe, non per ogni $ myObject = new third_party_library ()

Ora, più avanti nel tuo codice o in un'altra classe - ogni volta che la logica raggiunge un punto in cui devi sovrascrivere la funzione - fai semplicemente come segue:

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

Come nota a margine, se esegui tutte le funzioni della tua classe in questo modo e usi un archivio di chiavi / valori basato su stringhe come public static $ Functions ['function_name'] = function (...) {.. .}; può essere utile per riflettere. Non tanto in PHP quanto in altre lingue, perché puoi già afferrare i nomi di classe e funzione, ma puoi salvare alcune elaborazioni e i futuri utenti della tua classe possono usare le sostituzioni in PHP. Si tratta comunque di un ulteriore livello di riferimento indiretto, quindi eviterei di usarlo su classi primitive laddove possibile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top