107 votes

Comment ajouter une nouvelle méthode à un objet PHP en cours d'exécution ?

Comment ajouter une nouvelle méthode à un objet "sur le pouce" ?

$moi = new stdClass;
$moi->doSomething = function () {
    echo 'J\'ai fait quelque chose';
};
$moi->doSomething();

//Erreur fatale : Appel à une méthode non définie stdClass::doSomething()

0 votes

Est-ce spécifiquement sans utiliser une classe?

1 votes

Vous pourriez utiliser __call. Mais s'il vous plaît, ne l'utilisez pas. Changer dynamiquement le comportement d'un objet est un moyen très facile de rendre votre code illisible et non maintenable.

102voto

karim79 Points 178055

Vous pouvez utiliser __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 "Bonjour, cette fonction est ajoutée à l'exécution"; };
$foo->bar();

6 votes

Très, très intelligent mais maladroit dans un projet du monde réel à mon avis! (et +1 pour contrer l'utilisateur anonyme qui a voté négativement)

2 votes

@pekka - mais comment, exactement, est-ce bricolé? Je ne dis pas que je suis un expert en extension d'objet pendant l'exécution en PHP, mais honnêtement je ne vois pas grand-chose de mal avec ça. (peut-être ai-je juste mauvais goût)

0 votes

@karim C'est simplement le sentiment général que ce n'est pas à quoi __call était destiné, et il pourrait déjà être utilisé autrement. Je me sentirais mal à l'aise d'utiliser cela d'un point de vue style de codage. D'un autre côté, c'est élégant, peut être étendu avec des vérifications d'erreur (comme lorsque $method n'existe pas) et pourrait être défini dans une classe centrale quelque part pour être ensuite transmis à tous les objets.

24voto

alexroat Points 441

En utilisant simplement __call pour permettre l'ajout de nouvelles méthodes à l'exécution a pour inconvénient majeur que ces méthodes ne peuvent pas utiliser la référence d'instance $this. Tout fonctionne bien, tant 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

Pour 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"

12voto

Pekka 웃 Points 249607

Mise à jour : L'approche présentée ici a un inconvénient majeur : la nouvelle fonction n'est pas un membre entièrement qualifié de la classe ; $this n'est pas présent dans la méthode lorsqu'elle est appelée de cette manière. Cela signifie que vous devriez passer l'objet à la fonction en tant que paramètre si vous souhaitez travailler avec des données ou des fonctions de l'instance de l'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 astucieuse d'utiliser les nouvelles fonctions anonymes !

De manière intéressante, cela fonctionne : Remplacez

$me->doSomething();    // Ne fonctionne pas

par call_user_func sur la fonction elle-même :

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

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

call_user_func(array($me, "doSomething"));   // Ne fonctionne pas

si appelé de cette manière, PHP exige que la méthode soit déclarée dans la définition de la classe.

S'agit-il d'une question de visibilité private / public / protected ?

Mise à jour : Non. Il est impossible d'appeler la fonction de la manière habituelle même depuis l'intérieur de la classe, donc ce n'est pas une question de visibilité. Passer la fonction réelle à call_user_func() est la seule façon que je trouve pour que cela fonctionne.

3voto

Gino Points 11

Voici une classe simple qui permet de définir une fonction anonyme. C'est une classe optimisée de https://gist.github.com/nickunderscoremok/5857846

 $argument) {
                if ($argument instanceOf Closure) {
                    $this->{$property} = $argument;
                } else {
                    $this->{$property} = $argument;
                }
            }
        }
    }

    public function __call($method, $arguments) {
        if (isset($this->{$method}) && is_callable($this->{$method})) {
            return call_user_func_array($this->{$method}, $arguments);
        } else {
            throw new Exception("Erreur fatale : Appel à une méthode non définie dans stdObject::{$method}()");
        }
    }
}

$person = new stdObject(array(
    "name" => "nick",
    "age" => 23,
    "friends" => array("frank", "sally", "aaron"),
    "sayHi" => function() {
        return "Bonjour";
    }
));

$person->sayHi2 = function() {
    return "Bonjour 2";
};

$person->test = function() {
    return "test";
};

var_dump($person->name, $person->test(), $person->sayHi2());
?>

0voto

Zilverdistel Points 792

Il y a un post similaire sur stackoverflow qui clarifie que cela n'est réalisable qu'à travers la mise en œuvre de certains design patterns.

L'unique autre moyen est par l'utilisation de classkit, une extension php expérimentale. (aussi dans le post)

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X