web-dev-qa-db-fra.com

Redéfinir les méthodes de classe ou la classe

Existe-t-il un moyen de redéfinir une classe ou certaines de ses méthodes sans utiliser un héritage typique? Par exemple:

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

Que puis-je faire pour remplacer buggy_function()? Évidemment c'est ce que j'aimerais faire

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

C'est mon dilemme exact: j'ai mis à jour une bibliothèque tierce qui casse mon code. Je ne souhaite pas modifier directement la bibliothèque, car les futures mises à jour pourraient à nouveau effacer le code. Je cherche un moyen simple de remplacer la méthode de classe. 

J'ai trouvé cette bibliothèque qui dit que ça peut le faire, mais je suis méfiant car il a 4 ans.

MODIFIER:

J'aurais dû préciser que je ne peux pas renommer la classe de third_party_library à magical_third_party_library ou quoi que ce soit d'autre à cause des limitations de l'infrastructure.

Pour mes besoins, serait-il possible d'ajouter une fonction à la classe? Je pense que vous pouvez le faire en C # avec quelque chose appelé "classe partielle".

42
SeanDowney

Cela s'appelle singe patching . Mais PHP n’a pas de support natif pour cela.

Cependant, comme d'autres l'ont également souligné, la bibliothèque du kit de développement est disponible pour ajouter un support à la langue et est le successeur de classkit . Et, bien qu'il semble avoir été abandonné par son créateur (après avoir déclaré qu'il n'était pas compatible avec PHP 5.2 et versions ultérieures), le projet semble maintenant avoir un nouveau maison et mainteneur .

Je suis toujours je ne peux pas dire que je suis fan de son approche. Faire des modifications en évaluant des chaînes de code m'a toujours semblé potentiellement dangereux et difficile à déboguer.

Néanmoins, runkit_method_redefine semble être ce que vous recherchez, et un exemple de son utilisation peut être trouvé dans /tests/runkit_method_redefine.phpt dans le référentiel:

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

runkit semble être une bonne solution, mais il n'est pas activé par défaut et certaines parties sont encore expérimentales. J'ai donc piraté une petite classe qui remplace les définitions de fonctions dans un fichier de classe. Exemple d'utilisation:

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

Ensuite, incluez la classe à modifier et redéfinissez ses méthodes:

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

Puis évaluez le nouveau code:

eval($objPatch->getCode());

Un peu brut mais ça marche!

14
JPhilly

Par souci d’exhaustivité, les correctifs pour les singes sont disponibles dans PHP via runkit . Pour plus de détails, voir runkit_method_redefine().

11
Till

Oui, ça s'appelle extend:

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

J'ai préfixé "sd". ;-)

Gardez à l'esprit que lorsque vous étendez une classe pour remplacer des méthodes, la signature de la méthode doit correspondre à l'original. Ainsi, par exemple, si l'original dit buggy_function($foo, $bar), il doit correspondre aux paramètres de la classe qui l'étend.

PHP est très bavard à ce sujet.

8
Till

Que diriez-vous de l'envelopper dans une autre classe comme

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

Pour les personnes qui cherchent encore cette réponse.

Vous devez utiliser extend en combinaison avec namespaces .

comme ça:

namespace MyCustomName;

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

Puis l'utiliser comme ceci:

use MyCustomName\third_party_library;

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

Zend Studio et PDT (basé sur Eclipse ide) ont des outils de réfraction intégrés. Mais il n'y a pas de méthodes intégrées pour le faire.

De plus, vous ne voudriez pas avoir de mauvais code dans votre système. Puisqu'il pourrait être appelé par erreur.

1
Ólafur Waage

Il y a toujours moyen d'étendre la classe avec une nouvelle méthode appropriée et d'appeler cette classe à la place de la classe buggy.

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

(Désolé pour le formatage merdique)

0
Eric Lamb

Si la bibliothèque crée explicitement la mauvaise classe et n'utilise pas de localisateur ou de système de dépendance, vous n'avez pas de chance. Il n’existe aucun moyen de remplacer une méthode sur une autre classe à moins d’avoir sous-classe . La solution peut consister à créer un fichier de correctif qui corrige la bibliothèque afin de pouvoir la mettre à niveau et appliquer à nouveau le correctif pour corriger cette méthode.

0
MOdMac

Vous pourrez peut-être faire cela avec runkit. http://php.net/runkit

0
Toxygene

Vous pouvez faire une copie de la classe de la bibliothèque, avec tout le même sauf le nom de la classe. Puis remplacez cette classe renommée.

Ce n'est pas parfait, mais cela améliore la visibilité des changements de la classe qui s'étend. Si vous récupérez la bibliothèque avec quelque chose comme Composer, vous devez valider la copie dans le contrôle de source et la mettre à jour lorsque vous mettez à jour la bibliothèque.

Dans mon cas, il s’agissait d’une ancienne version de https://github.com/bshaffer/oauth2-server-php . J'ai modifié l'autoloader de la bibliothèque pour récupérer mon fichier de classe à la place. Mon fichier de classe a repris le nom d'origine et a étendu une version copiée de l'un des fichiers.

0
Ben Creasy

Puisque vous avez toujours accès au code de base en PHP, redéfinissez les fonctions de classe principales que vous souhaitez remplacer, ceci devrait laisser vos interfaces intactes:

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

Vous pouvez pour une raison quelconque utiliser une variable privée, mais vous ne pourrez alors accéder à la fonction qu'en étendant la classe ou la logique à l'intérieur de la classe. De même, il est possible que vous souhaitiez que différents objets de la même classe aient des fonctions différentes. Si c'est le cas, n'utilisez pas statique, mais vous voulez généralement qu'il soit statique afin de ne pas dupliquer l'utilisation de la mémoire pour chaque objet créé. Le code 'ranOnce' s'assure simplement que vous ne devez l'initialiser qu'une seule fois pour la classe, pas pour chaque $myObject = new third_party_library().

Maintenant, plus tard dans votre code ou une autre classe - chaque fois que la logique atteint un point où vous devez remplacer la fonction - procédez comme suit:

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

En remarque, si vous utilisez toutes les fonctions de classe de cette manière et utilisez un magasin de clés/valeurs basé sur des chaînes tel que public static $functions['function_name'] = function(...){...};, cela peut être utile pour la réflexion. Pas autant dans PHP que dans d'autres langages, car vous pouvez déjà saisir les noms de classe et de fonction, mais vous pouvez enregistrer une partie du traitement et les futurs utilisateurs de votre classe peuvent utiliser des remplacements en PHP. Il s’agit toutefois d’un niveau supplémentaire d’indirection, de sorte que j’évite de l’utiliser autant que possible sur des classes primitives.

0
Garet Claborn