Como adiciono um novo método a um objeto "em tempo real"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Como adiciono um novo método a um objeto "em tempo real"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Respostas:
Você pode aproveitar __call
para isso:
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 da chamada do método, de modo que a função obtenha uma referência ao objeto sem a necessidade de vinculá-lo explicitamente ...
Com o PHP 7 você pode usar classes anônimas
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Usar simplesmente __call para permitir a adição de novos métodos em tempo de execução tem a principal desvantagem de que esses métodos não podem usar a referência $ this instância. Tudo funciona muito bem, até que os métodos adicionados não usem $ this no 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, você pode reescrever o padrão desta forma
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));
}
}
Desta forma, você pode adicionar novos métodos que podem usar a referência de instância
$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"
Atualização: a abordagem mostrada aqui tem uma grande lacuna: a nova função não é um membro totalmente qualificado da classe;
$this
não está presente no método quando chamado dessa maneira. Isso significa que você teria que passar o objeto para a função como um parâmetro se quiser trabalhar com dados ou funções da instância do objeto! Além disso, você não poderá acessarprivate
ouprotected
membros da classe a partir dessas funções.
Boa pergunta e ideia inteligente usando as novas funções anônimas!
Curiosamente, isso funciona: Substitua
$me->doSomething(); // Doesn't work
por call_user_func na própria função :
call_user_func($me->doSomething); // Works!
o que não funciona é a maneira "certa":
call_user_func(array($me, "doSomething")); // Doesn't work
se chamado dessa forma, o PHP requer que o método seja declarado na definição da classe.
Isso é um private
problema de visibilidade / public
/ protected
?
Atualização: Não. É impossível chamar a função da maneira normal mesmo de dentro da classe, portanto, esse não é um problema de visibilidade. Passar a função real para call_user_func()
é a única maneira de fazer isso funcionar.
você também pode salvar funções em uma 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 como fazer isso com eval, você pode dar uma olhada no meu micro-framework PHP, Halcyon, que está disponível no github . É pequeno o suficiente para que você consiga descobrir sem problemas - concentre-se na classe HalcyonClassMunger.
Sem a __call
solução, você pode usar bindTo
(PHP> = 5.4), para chamar o método com $this
limite da $me
seguinte maneira:
call_user_func($me->doSomething->bindTo($me, null));
O script completo pode ter a seguinte aparência:
$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"
Como alternativa, você pode atribuir a função vinculada a uma variável e chamá-la sem call_user_func
:
$f = $me->doSomething->bindTo($me);
$f();
Há uma postagem semelhante sobre stackoverflow que esclarece que isso só é possível por meio da implementação de certos padrões de design.
A única outra maneira é através do uso de classkit , uma extensão experimental de php. (também na postagem)
Sim, é possível adicionar um método a uma classe PHP após sua definição. Você deseja usar o classkit, que é uma extensão "experimental". Parece que esta extensão não está habilitada por padrão, portanto, depende se você pode compilar um binário PHP personalizado ou carregar DLLs PHP se estiver no Windows (por exemplo, o Dreamhost permite binários PHP personalizados e eles são muito fáceis de configurar )
A resposta karim79 funciona, mas armazena funções anônimas dentro das propriedades do método e suas declarações podem sobrescrever as propriedades existentes com o mesmo nome ou não funcionam se a propriedade existente está private
causando erro fatal. Objeto inutilizável. Acho que armazená-los em um array separado e usar setter é uma solução mais limpa. O método injetor setter pode ser adicionado a cada objeto automaticamente usando traits .
PS Claro que este é um hack que não deve ser usado, pois viola o princípio aberto fechado 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);