Wie füge ich einem Objekt "on the fly" eine neue Methode hinzu?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Wie füge ich einem Objekt "on the fly" eine neue Methode hinzu?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Antworten:
Sie können __call
dies nutzen:
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();
array_unshift($args, $this);
vor dem Methodenaufruf einen aus, damit die Funktion einen Verweis auf das Objekt erhält, ohne es explizit binden zu müssen ...
Mit PHP 7 können Sie anonyme Klassen verwenden
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Die Verwendung von einfach __call, um das Hinzufügen neuer Methoden zur Laufzeit zu ermöglichen, hat den Hauptnachteil, dass diese Methoden die $ this-Instanzreferenz nicht verwenden können. Alles funktioniert hervorragend, bis die hinzugefügten Methoden $ this nicht im Code verwenden.
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
Um dieses Problem zu lösen, können Sie das Muster auf diese Weise neu schreiben
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));
}
}
Auf diese Weise können Sie neue Methoden hinzufügen, die die Instanzreferenz verwenden können
$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"
Update: Der hier gezeigte Ansatz weist ein großes Manko auf: Die neue Funktion ist kein voll qualifiziertes Mitglied der Klasse.
$this
ist in der Methode nicht vorhanden, wenn sie auf diese Weise aufgerufen wird. Dies bedeutet, dass Sie das Objekt als Parameter an die Funktion übergeben müssen, wenn Sie mit Daten oder Funktionen aus der Objektinstanz arbeiten möchten! Außerdem können Sie über diese Funktionen nicht aufprivate
oderprotected
Mitglieder der Klasse zugreifen .
Gute Frage und clevere Idee mit den neuen anonymen Funktionen!
Interessanterweise funktioniert dies: Ersetzen
$me->doSomething(); // Doesn't work
von call_user_func für die Funktion selbst :
call_user_func($me->doSomething); // Works!
Was nicht funktioniert, ist der "richtige" Weg:
call_user_func(array($me, "doSomething")); // Doesn't work
Wenn PHP so aufgerufen wird, muss die Methode in der Klassendefinition deklariert werden.
Ist das ein private
/ public
/ protected
Sichtbarkeitsproblem?
Update: Nein. Es ist unmöglich, die Funktion auch innerhalb der Klasse auf normale Weise aufzurufen, daher ist dies kein Sichtbarkeitsproblem. call_user_func()
Nur wenn ich die eigentliche Funktion an übergebe , kann ich scheinen, dass dies funktioniert.
Sie können Funktionen auch in einem Array speichern:
<?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);
?>
Um zu sehen, wie dies mit eval gemacht wird, können Sie sich mein PHP-Mikro-Framework Halcyon ansehen, das auf github verfügbar ist . Es ist klein genug, damit Sie es problemlos herausfinden können - konzentrieren Sie sich auf die HalcyonClassMunger-Klasse.
Ohne die __call
Lösung können Sie bindTo
(PHP> = 5.4) verwenden, um die Methode mit $this
gebundenem Wert $me
wie folgt aufzurufen :
call_user_func($me->doSomething->bindTo($me, null));
Das vollständige Skript könnte folgendermaßen aussehen:
$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"
Alternativ können Sie die gebundene Funktion einer Variablen zuweisen und sie dann aufrufen, ohne call_user_func
:
$f = $me->doSomething->bindTo($me);
$f();
Es gibt einen ähnlichen Beitrag zum Stapelüberlauf , der klarstellt , dass dies nur durch die Implementierung bestimmter Entwurfsmuster erreichbar ist.
Der einzige andere Weg ist die Verwendung von Classkit , einer experimentellen PHP-Erweiterung. (auch in der Post)
Ja, es ist möglich, einer PHP-Klasse eine Methode hinzuzufügen, nachdem sie definiert wurde. Sie möchten das Classkit verwenden, eine "experimentelle" Erweiterung. Es scheint jedoch, dass diese Erweiterung nicht standardmäßig aktiviert ist. Es hängt also davon ab, ob Sie eine benutzerdefinierte PHP-Binärdatei kompilieren oder unter Windows PHP-DLLs laden können (Dreamhost erlaubt beispielsweise benutzerdefinierte PHP-Binärdateien und sie sind recht einfach einzurichten ).
karim79 answer funktioniert, speichert jedoch anonyme Funktionen in Methodeneigenschaften und seine Deklarationen können vorhandene Eigenschaften mit demselben Namen überschreiben oder funktionieren nicht, wenn vorhandene Eigenschaften private
schwerwiegende Fehlerobjekte verursachen, die unbrauchbar sind. Ich denke, sie in einem separaten Array zu speichern und setter zu verwenden, ist eine sauberere Lösung. Der Methodeninjektorsetzer kann jedem Objekt automatisch mithilfe von Merkmalen hinzugefügt werden .
PS Natürlich ist dies ein Hack, der nicht verwendet werden sollte, da er gegen das Open-Closed-Prinzip von SOLID verstößt.
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);