Question

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 je voudrais 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 pouvoir le faire, mais je suis méfiant. comme il a 4 ans.

EDIT:

J'aurais dû préciser que je ne peux pas renommer la classe de third_party_library en 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".

Était-ce utile?

La solution

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

Bien que, comme d'autres l'ont également souligné, la bibliothèque runkit soit disponible pour ajouter du 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 désormais avoir un nouvelle maison et responsable de la maintenance .

Je ne peux toujours 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.

Toujours, runkit_method_redefine semble être ce que vous recherchez, et un exemple d'utilisation est disponible dans /tests/runkit_method_redefine.phpt dans le référentiel:

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

Autres conseils

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 

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:

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

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

eval($objPatch->getCode());

Évaluez ensuite le nouveau code:

<*>

Un peu brut mais ça marche!

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:

<*>

Évaluez ensuite le nouveau code:

<*>

Un peu brut mais ça marche!

Par souci d'exhaustivité, les correctifs monkey sont disponibles en PHP via le runkit . Pour plus de détails, voir runkit_method_redefine () .

Pourquoi ne pas 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);
 }
}

Oui, cela 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é par "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.

Pour les personnes qui recherchent encore cette réponse.

Vous devez utiliser extend en combinaison avec espaces de noms .

comme ceci:

namespace MyCustomName;

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

Ensuite, pour l'utiliser, procédez comme suit:

use MyCustomName\third_party_library;

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

Zend Studio et PDT (eclipse based ide) ont des outils de réfraction intégrés. Mais il n’existe pas de méthode intégrée pour le faire.

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

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'y a aucun moyen de remplacer une méthode sur une autre classe à moins de sous-classer. La solution peut être de créer un fichier de correctif qui corrige la bibliothèque afin de pouvoir la mettre à niveau et réappliquer le correctif pour corriger cette méthode spécifique.

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

On étend toujours la classe avec une nouvelle méthode appropriée et on appelle 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)

Vous pouvez faire une copie de la classe de la bibliothèque, avec toutes les mêmes choses 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 code 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.

Etant donné que 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 simplement 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 réalisez toutes vos fonctions de classe de cette manière, utilisez un magasin de clé / valeur basé sur une chaîne, comme public static $ functions ['nom_fonction'] = fonction (...) {.. .}; cela peut être utile pour la réflexion. Pas autant en PHP que dans d'autres langages, car vous pouvez déjà récupérer les noms de classe et de fonction, mais vous pouvez enregistrer certains traitements 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’éviterais de l’utiliser autant que possible dans des classes primitives.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top