web-dev-qa-db-fra.com

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

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()
90
MadBad

Vous pouvez exploiter __call pour 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();
92
karim79

Avec PHP 7 vous pouvez utiliser des classes anonymes

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

$myObject->myFunction();

PHP RFC: classes anonymes

41
Mohamed Ramrami

L'utilisation de simplement __call afin de permettre l'ajout de nouvelles méthodes au moment de l'exécution présente l'inconvénient majeur que ces méthodes ne peuvent pas utiliser la référence $ this instance. 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 modèle 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"
18
alexroat

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; $this N'est pas présent dans la méthode lorsqu'elle est invoquée de cette façon. 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! De plus, vous ne pourrez pas accéder aux membres private ou protected de la classe à partir de ces fonctions.

Bonne question et idée intelligente en 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, c'est la "bonne" façon:

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

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

S'agit-il d'un private/public/protected problème de visibilité?

Mise à jour: Non. Il est impossible d'appeler la fonction de manière normale, même à l'intérieur 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.

12
Pekka 웃

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);
?>
2
miglio

Sans le __call solution, vous pouvez utiliser bindTo (PHP> = 5.4), pour appeler la méthode avec $this lié à $me comme ça:

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(); 
1
trincot

Pour voir comment faire cela avec eval, vous pouvez jeter un oeil à mon PHP micro-framework, Halcyon, qui est disponible sur github . Il est assez petit pour que vous devrait être en mesure de le comprendre sans aucun problème - concentrez-vous sur la classe HalcyonClassMunger.

1
ivans

karim79 la réponse fonctionne mais elle stocke des 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 fonctionnent pas si la propriété existante est private provoquant un objet d'erreur fatale 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é à chaque objet automatiquement en utilisant traits .

P.S. Bien sûr, c'est un hack qui ne doit pas être utilisé car il viole le principe ouvert et 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);
0
Roman Toasov

Il y a un article similaire sur stackoverflow qui indique que cela n'est possible que par la mise en œuvre de certains modèles de conception.

La seule autre façon est d'utiliser classkit , une extension php expérimentale. (également dans la poste)

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 par défaut cependant, cela dépend si vous pouvez compiler un PHP binaire personnalisé ou charger PHP DLL si sur Windows (par exemple Dreamhost autorise la personnalisation PHP binaires, et ils sont assez faciles à installer).

0
Zilverdistel