¿Cómo agrego un nuevo método a un objeto "sobre la marcha"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
¿Cómo agrego un nuevo método a un objeto "sobre la marcha"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Respuestas:
Puede aprovechar __call
para esto:
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);
antes de la llamada al método, de modo que la función obtenga una referencia al objeto sin necesidad explícita de vincularlo ...
Con PHP 7 puedes usar clases anónimas
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Usar simplemente __call para permitir agregar nuevos métodos en tiempo de ejecución tiene el mayor inconveniente de que esos métodos no pueden usar la referencia de instancia $ this. Todo funciona muy bien, hasta que los métodos agregados no usen $ this en el código.
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
Para resolver este problema, puede reescribir el patrón de esta manera
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 esta manera, puede agregar nuevos métodos que pueden usar la referencia de instancia
$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"
Actualización: el enfoque que se muestra aquí tiene una deficiencia importante: la nueva función no es un miembro completamente calificado de la clase;
$this
no está presente en el método cuando se invoca de esta manera. Esto significa que tendría que pasar el objeto a la función como parámetro si desea trabajar con datos o funciones de la instancia del objeto. Además, usted no será capaz de accederprivate
o deprotected
los miembros de la clase a partir de estas funciones.
¡Buena pregunta e inteligente idea usando las nuevas funciones anónimas!
Curiosamente, esto funciona: Reemplazar
$me->doSomething(); // Doesn't work
por call_user_func en la función en sí :
call_user_func($me->doSomething); // Works!
lo que no funciona es la forma "correcta":
call_user_func(array($me, "doSomething")); // Doesn't work
si se llama de esa manera, PHP requiere que el método se declare en la definición de la clase.
¿Es esta una private
/ public
/ protected
tema visibilidad?
Actualización: No. Es imposible llamar a la función de la forma normal incluso desde dentro de la clase, por lo que esto no es un problema de visibilidad. Pasar la función real a call_user_func()
es la única forma en que puedo hacer que esto funcione.
también puede guardar funciones en una matriz:
<?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);
?>
Para ver cómo hacer esto con eval, puede echar un vistazo a mi micro-framework PHP, Halcyon, que está disponible en github . Es lo suficientemente pequeño como para poder resolverlo sin ningún problema: concéntrese en la clase HalcyonClassMunger.
Sin la __call
solución, puede usar bindTo
(PHP> = 5.4), para llamar al método con un $this
límite $me
así:
call_user_func($me->doSomething->bindTo($me, null));
El guión completo podría verse así:
$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"
Alternativamente, puede asignar la función vinculada a una variable y luego llamarla sin call_user_func
:
$f = $me->doSomething->bindTo($me);
$f();
Hay una publicación similar en stackoverflow que aclara que esto solo se puede lograr mediante la implementación de ciertos patrones de diseño.
La única otra forma es mediante el uso de classkit , una extensión experimental de php. (también en la publicación)
Sí, es posible agregar un método a una clase PHP después de su definición. Quieres usar classkit, que es una extensión "experimental". Sin embargo, parece que esta extensión no está habilitada de forma predeterminada, por lo que depende de si puede compilar un binario PHP personalizado o cargar archivos DLL PHP si está en Windows (por ejemplo, Dreamhost permite binarios PHP personalizados, y son bastante fáciles de configurar ).
La respuesta karim79 funciona, sin embargo, almacena funciones anónimas dentro de las propiedades del método y sus declaraciones pueden sobrescribir las propiedades existentes del mismo nombre o no funciona si la propiedad existente está private
causando un error fatal en el objeto inutilizable.Creo que almacenarlos en una matriz separada y usar setter es una solución más limpia. El setter inyector de método se puede agregar a cada objeto automáticamente usando rasgos .
PD: Por supuesto, este es un truco que no debe usarse ya que viola el principio abierto cerrado 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);