Come si aggiunge un nuovo metodo a un oggetto "al volo"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Come si aggiunge un nuovo metodo a un oggetto "al volo"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Risposte:
Puoi sfruttare __call
per questo:
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);
chiamata prima della chiamata al metodo, in modo che la funzione ottenga un riferimento all'oggetto senza bisogno esplicitamente di vincolarlo ...
Con PHP 7 puoi usare classi anonime
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Usare semplicemente __call per consentire l'aggiunta di nuovi metodi in fase di esecuzione ha il principale svantaggio che questi metodi non possono usare il riferimento $ this instance. Tutto funziona alla grande, fino a quando i metodi aggiunti non usano $ this nel codice.
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
Per risolvere questo problema puoi riscrivere il pattern in questo modo
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));
}
}
In questo modo puoi aggiungere nuovi metodi che possono utilizzare il riferimento all'istanza
$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"
Aggiornamento: l'approccio mostrato qui ha un grave difetto: la nuova funzione non è un membro pienamente qualificato della classe;
$this
non è presente nel metodo quando viene richiamato in questo modo. Ciò significa che dovresti passare l'oggetto alla funzione come parametro se vuoi lavorare con dati o funzioni dall'istanza dell'oggetto! Inoltre, non sarà in grado di accedereprivate
o diprotected
membri della classe da queste funzioni.
Bella domanda e idea intelligente utilizzando le nuove funzioni anonime!
È interessante notare che funziona: sostituire
$me->doSomething(); // Doesn't work
da call_user_func sulla funzione stessa :
call_user_func($me->doSomething); // Works!
quello che non funziona è il modo "giusto":
call_user_func(array($me, "doSomething")); // Doesn't work
se chiamato in questo modo, PHP richiede che il metodo venga dichiarato nella definizione della classe.
È un problema di visibilità private
/ public
/ protected
?
Aggiornamento: no. È impossibile chiamare la funzione nel modo normale anche dall'interno della classe, quindi questo non è un problema di visibilità. Passare la funzione effettiva a call_user_func()
è l'unico modo in cui riesco a farlo funzionare.
puoi anche salvare le funzioni in un array:
<?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);
?>
Per vedere come farlo con eval, puoi dare un'occhiata al mio micro-framework PHP, Halcyon, che è disponibile su GitHub . È abbastanza piccolo da essere in grado di capirlo senza problemi: concentrati sulla classe HalcyonClassMunger.
Senza la __call
soluzione, puoi usare bindTo
(PHP> = 5.4), per chiamare il metodo con $this
associato in $me
questo modo:
call_user_func($me->doSomething->bindTo($me, null));
Lo script completo potrebbe assomigliare a questo:
$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"
In alternativa, puoi assegnare la funzione associata a una variabile e quindi chiamarla senza call_user_func
:
$f = $me->doSomething->bindTo($me);
$f();
C'è un post simile su stackoverflow che chiarisce che questo è possibile solo attraverso l'implementazione di determinati modelli di progettazione.
L'unico altro modo è attraverso l'uso di classkit , un'estensione php sperimentale. (anche nel post)
Sì, è possibile aggiungere un metodo a una classe PHP dopo che è stata definita. Vuoi usare classkit, che è un'estensione "sperimentale". Sembra che questa estensione non sia abilitata per impostazione predefinita, quindi dipende dalla possibilità di compilare un binario PHP personalizzato o caricare DLL PHP se su Windows (ad esempio Dreamhost consente binari PHP personalizzati e sono piuttosto facili da configurare ).
La risposta di karim79 funziona tuttavia memorizza le funzioni anonime all'interno delle proprietà del metodo e le sue dichiarazioni possono sovrascrivere le proprietà esistenti con lo stesso nome o non funziona se la proprietà esistente private
causa un errore fatale oggetto inutilizzabile, penso che memorizzarle in un array separato e usare setter sia una soluzione più pulita. Il setter dell'iniettore del metodo può essere aggiunto automaticamente a ogni oggetto utilizzando i tratti .
PS Ovviamente questo è un trucco che non dovrebbe essere usato in quanto viola il principio aperto chiuso di 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);