Jak dodać nową metodę do obiektu „w locie”?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Jak dodać nową metodę do obiektu „w locie”?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Odpowiedzi:
Możesz to wykorzystać __call:
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);przed wywołaniem metody, aby funkcja otrzymała odniesienie do obiektu bez konieczności jawnego wiązania go ...
W PHP 7 możesz używać anonimowych klas
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Używanie prostego __call w celu umożliwienia dodawania nowych metod w czasie wykonywania ma tę główną wadę, że te metody nie mogą używać odwołania $ this do instancji. Wszystko działa świetnie, dopóki dodane metody nie używają $ this w kodzie.
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
Aby rozwiązać ten problem, możesz przepisać wzór w ten sposób
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));
}
}
W ten sposób możesz dodać nowe metody, które mogą używać odwołania do instancji
$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"
Aktualizacja: Przedstawione tutaj podejście ma poważną wadę: nowa funkcja nie jest w pełni wykwalifikowanym członkiem klasy;
$thisnie występuje w metodzie, gdy jest wywoływana w ten sposób. Oznacza to, że musisz przekazać obiekt do funkcji jako parametr, jeśli chcesz pracować z danymi lub funkcjami z instancji obiektu! Ponadto nie będzie można uzyskać dostępuprivateaniprotectedczłonków klasy z tych funkcji.
Dobre pytanie i sprytny pomysł z wykorzystaniem nowych anonimowych funkcji!
Co ciekawe, to działa: Wymień
$me->doSomething(); // Doesn't work
przez call_user_func na samej funkcji :
call_user_func($me->doSomething); // Works!
to, co nie działa, to „właściwy” sposób:
call_user_func(array($me, "doSomething")); // Doesn't work
jeśli tak się nazywa, PHP wymaga zadeklarowania metody w definicji klasy.
Czy jest to problem z widocznością private/ public/ protected?
Aktualizacja: nie . Niemożliwe jest wywołanie funkcji w normalny sposób, nawet z poziomu klasy, więc nie jest to problem z widocznością. Przekazanie faktycznej funkcji call_user_func()to jedyny sposób, w jaki mogę to zrobić.
możesz także zapisać funkcje w tablicy:
<?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);
?>
Aby zobaczyć, jak to zrobić za pomocą eval, możesz rzucić okiem na mój mikro-framework PHP, Halcyon, który jest dostępny na github . Jest na tyle mały, że powinieneś być w stanie to rozgryźć bez żadnych problemów - skoncentruj się na klasie HalcyonClassMunger.
Bez __callrozwiązania możesz użyć bindTo(PHP> = 5.4), aby wywołać metodę z $thispowiązaniem w $menastępujący sposób:
call_user_func($me->doSomething->bindTo($me, null));
Kompletny skrypt mógłby wyglądać następująco:
$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"
Alternatywnie możesz przypisać powiązaną funkcję do zmiennej, a następnie wywołać ją bez call_user_func:
$f = $me->doSomething->bindTo($me);
$f();
Jest podobny post w stackoverflow, który wyjaśnia, że jest to osiągalne tylko poprzez implementację pewnych wzorców projektowych.
Jedynym innym sposobem jest użycie classkit , eksperymentalnego rozszerzenia php. (również w poście)
Tak, istnieje możliwość dodania metody do klasy PHP po jej zdefiniowaniu. Chcesz użyć zestawu klas, który jest rozszerzeniem „eksperymentalnym”. Wygląda na to, że to rozszerzenie nie jest domyślnie włączone, więc zależy to od tego, czy możesz skompilować niestandardowy plik binarny PHP lub załadować biblioteki DLL PHP w systemie Windows (na przykład Dreamhost zezwala na niestandardowe pliki binarne PHP i są one dość łatwe do skonfigurowania ).
odpowiedź karim79 działa, ale przechowuje anonimowe funkcje we właściwościach metody, a jego deklaracje mogą nadpisywać istniejące właściwości o tej samej nazwie lub nie działa, jeśli istniejąca właściwość privatepowoduje błąd krytyczny, obiekt jest bezużyteczny. metodę ustawiającą wtryskiwacz można dodać do każdego obiektu automatycznie przy użyciu cech .
PS Oczywiście jest to hack, którego nie należy używać, ponieważ narusza on zasadę open closed 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);