Comment ajouter une nouvelle méthode à un objet php à la volée?

98

Comment ajouter une nouvelle méthode à un objet "à la volée"?

$me= new stdClass;
$me->doSomething=function ()
 {
    echo 'I\'ve done something';
 };
$me->doSomething();

//Fatal error: Call to undefined method stdClass::doSomething()
MadBad
la source
Est-ce spécifiquement sans utiliser de classe?
thomas-peter
1
Vous pouvez utiliser __call. Mais veuillez ne pas utiliser __call. La modification dynamique du comportement d'un objet est un moyen très simple de rendre votre code illisible et non maintenable.
GordonM
Vous pouvez utiliser cet outil: packagist.org/packages/drupol/dynamicobjects
Pol Dellaiera

Réponses:

94

Vous pouvez exploiter __callpour cela:

class Foo
{
    public function __call($method, $args)
    {
        if (isset($this->$method)) {
            $func = $this->$method;
            return call_user_func_array($func, $args);
        }
    }
}

$foo = new Foo();
$foo->bar = function () { echo "Hello, this function is added at runtime"; };
$foo->bar();
karim79
la source
6
Très, très intelligent mais kludgy dans un projet du monde réel IMO! (et +1 pour contrer le downvoter anonyme)
Pekka
2
@pekka - mais comment, exactement, est-ce kludgy? Bien sûr, je ne dis pas que je suis un expert dans l'extension d'un objet pendant l'exécution en PHP, mais honnêtement, je ne peux pas dire que je vois beaucoup de mal. (peut-être que j'ai juste un mauvais goût)
karim79
16
J'ajouterais une vérification is_callable dans ce cas également (donc vous n'essayez pas accidentellement d'appeler une chaîne). Et lancez également une BadMethodCallException () si vous ne trouvez pas de méthode, donc vous ne l'avez pas renvoyée et pensez qu'elle a réussi. De plus, faites du premier paramètre de la fonction l'objet lui-même, puis faites un array_unshift($args, $this);avant l'appel de la méthode, afin que la fonction obtienne une référence à l'objet sans avoir explicitement besoin de le lier ...
ircmaxell
3
Je pense que les gens manquent le point, l'exigence initiale était d'utiliser un objet sans classe.
thomas-peter
1
Je suggérerais en fait que vous supprimiez complètement le test isset () car si cette fonction n'est pas définie, vous ne voudrez probablement pas revenir en silence.
Alexis Wilke
50

Avec PHP 7, vous pouvez utiliser des classes anonymes

$myObject = new class {
    public function myFunction(){}
};

$myObject->myFunction();

PHP RFC: Classes anonymes

Mohamed Ramrami
la source
20

Utiliser simplement __call pour permettre l'ajout de nouvelles méthodes lors de l'exécution présente l'inconvénient majeur que ces méthodes ne peuvent pas utiliser la référence d'instance $ this. Tout fonctionne très bien, jusqu'à ce que les méthodes ajoutées n'utilisent pas $ this dans le code.

class AnObj extends stdClass
{
    public function __call($closure, $args)
    {
        return call_user_func_array($this->{$closure}, $args);
    }
 }
 $a=new AnObj();
 $a->color = "red";
 $a->sayhello = function(){ echo "hello!";};
 $a->printmycolor = function(){ echo $this->color;};
 $a->sayhello();//output: "hello!"
 $a->printmycolor();//ERROR: Undefined variable $this

Afin de résoudre ce problème, vous pouvez réécrire le motif de cette manière

class AnObj extends stdClass
{
    public function __call($closure, $args)
    {
        return call_user_func_array($this->{$closure}->bindTo($this),$args);
    }

    public function __toString()
    {
        return call_user_func($this->{"__toString"}->bindTo($this));
    }
}

De cette façon, vous pouvez ajouter de nouvelles méthodes qui peuvent utiliser la référence d'instance

$a=new AnObj();
$a->color="red";
$a->sayhello = function(){ echo "hello!";};
$a->printmycolor = function(){ echo $this->color;};
$a->sayhello();//output: "hello!"
$a->printmycolor();//output: "red"
alexroat
la source
12

Mise à jour: L'approche présentée ici présente un inconvénient majeur: la nouvelle fonction n'est pas un membre pleinement qualifié de la classe; $thisn'est pas présent dans la méthode lorsqu'elle est appelée de cette manière. Cela signifie que vous devrez passer l'objet à la fonction en tant que paramètre si vous souhaitez travailler avec des données ou des fonctions de l'instance d'objet! En outre, vous ne serez pas en mesure d'accéder privateou les protectedmembres de la classe de ces fonctions.

Bonne question et idée intelligente utilisant les nouvelles fonctions anonymes!

Fait intéressant, cela fonctionne: Remplacer

$me->doSomething();    // Doesn't work

par call_user_func sur la fonction elle - même :

call_user_func($me->doSomething);    // Works!

ce qui ne fonctionne pas est la "bonne" façon:

call_user_func(array($me, "doSomething"));   // Doesn't work

s'il est appelé de cette façon, PHP requiert que la méthode soit déclarée dans la définition de classe.

Est-ce un problème de visibilité private/ public/ protected?

Mise à jour: non . Il est impossible d'appeler la fonction de la manière normale même à partir de la classe, ce n'est donc pas un problème de visibilité. Passer la fonction réelle à call_user_func()est la seule façon dont je peux sembler faire ce travail.

Pekka
la source
2

vous pouvez également enregistrer des fonctions dans un tableau:

<?php
class Foo
{
    private $arrayFuncs=array();// array of functions
    //
    public function addFunc($name,$function){
        $this->arrayFuncs[$name] = $function;
    }
    //
    public function callFunc($namefunc,$params=false){
        if(!isset($this->arrayFuncs[$namefunc])){
            return 'no function exist';
        }
        if(is_callable($this->arrayFuncs[$namefunc])){
            return call_user_func($this->arrayFuncs[$namefunc],$params);
        }
    }

}
$foo = new Foo();

//Save function on array variable with params
$foo->addFunc('my_function_call',function($params){
    return array('data1'=>$params['num1'],'data2'=>'qwerty','action'=>'add');
});
//Save function on array variable
$foo->addFunc('my_function_call2',function(){
    return 'a simple function';
});
//call func 1
$data = $foo->callFunc('my_function_call',array('num1'=>1224343545));
var_dump($data);
//call func 2
$data = $foo->callFunc('my_function_call2');
var_dump($data);
?>
miglio
la source
1

Pour voir comment faire cela avec eval, vous pouvez jeter un œil à mon micro-framework PHP, Halcyon, qui est disponible sur github . Il est suffisamment petit pour que vous puissiez le comprendre sans aucun problème - concentrez-vous sur la classe HalcyonClassMunger.

Ivans
la source
Je suppose (tout comme mon code) que vous utilisez PHP <5.3.0 et que vous avez donc besoin de kludges et de hacks pour que cela fonctionne.
ivans
Demander si quelqu'un aimerait commenter le
vote défavorable
C'est probablement le cas, il y a un vote à la baisse de masse ici. Mais peut-être voulez-vous développer un peu ce que vous avez fait? Comme vous pouvez le voir, c'est une question intéressante sans réponse définitive.
Pekka
1

Sans la __callsolution, vous pouvez utiliser bindTo(PHP> = 5.4), pour appeler la méthode avec $thislié pour $meaimer ceci:

call_user_func($me->doSomething->bindTo($me, null)); 

Le script complet pourrait ressembler à ceci:

$me = new stdClass;
// Property for proving that the method has access to the above object:
$me->message = "I\'ve done something"; 
$me->doSomething = function () {
    echo $this->message;
};
call_user_func($me->doSomething->bindTo($me)); // "I've done something"

Vous pouvez également affecter la fonction liée à une variable, puis l'appeler sans call_user_func:

$f = $me->doSomething->bindTo($me);
$f(); 
trincot
la source
0

Il existe un article similaire sur stackoverflow qui indique que cela n'est réalisable que par la mise en œuvre de certains modèles de conception.

Le seul autre moyen est d'utiliser classkit , une extension php expérimentale. (également dans le post)

Oui, il est possible d'ajouter une méthode à une classe PHP après sa définition. Vous souhaitez utiliser classkit, qui est une extension "expérimentale". Il semble que cette extension ne soit pas activée par défaut cependant, cela dépend donc de la possibilité de compiler un binaire PHP personnalisé ou de charger des DLL PHP sous Windows (par exemple, Dreamhost autorise les binaires PHP personnalisés, et ils sont assez faciles à configurer ).

Zilverdistel
la source
0

La réponse karim79 fonctionne mais elle stocke les fonctions anonymes dans les propriétés de la méthode et ses déclarations peuvent écraser les propriétés existantes du même nom ou ne fonctionne pas si la propriété existante privateprovoque une erreur fatale objet inutilisable Je pense que les stocker dans un tableau séparé et utiliser setter est une solution plus propre. Le setter d'injecteur de méthode peut être ajouté automatiquement à chaque objet en utilisant des traits .

PS Bien sûr, c'est un hack qui ne doit pas être utilisé car il viole le principe ouvert fermé de SOLID.

class MyClass {

    //create array to store all injected methods
    private $anon_methods = array();

    //create setter to fill array with injected methods on runtime
    public function inject_method($name, $method) {
        $this->anon_methods[$name] = $method;
    }

    //runs when calling non existent or private methods from object instance
    public function __call($name, $args) {
        if ( key_exists($name, $this->anon_methods) ) {
            call_user_func_array($this->anon_methods[$name], $args);
        }
    }

}


$MyClass = new MyClass;

//method one
$print_txt = function ($text) {
    echo $text . PHP_EOL;
};

$MyClass->inject_method("print_txt", $print_txt);

//method
$add_numbers = function ($n, $e) {
    echo "$n + $e = " . ($n + $e);
};

$MyClass->inject_method("add_numbers", $add_numbers);


//Use injected methods
$MyClass->print_txt("Hello World");
$MyClass->add_numbers(5, 10);
Roman Toasov
la source
Si c'est un hack qui ne devrait pas être utilisé, il ne devrait pas être affiché comme réponse.
miken32