Как добавить новый метод к объекту «на лету»?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Как добавить новый метод к объекту «на лету»?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Ответы:
__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);
перед вызовом метода, чтобы функция получила ссылку на объект без явной необходимости его привязки ...
В PHP 7 вы можете использовать анонимные классы, что снимает stdClass
ограничение.
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Использование простого __call для добавления новых методов во время выполнения имеет главный недостаток, заключающийся в том, что эти методы не могут использовать ссылку на экземпляр $ this. Все отлично работает, пока добавленные методы не используют $ this в коде.
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
Чтобы решить эту проблему, вы можете переписать узор таким образом
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));
}
}
Таким образом вы можете добавлять новые методы, которые могут использовать ссылку на экземпляр
$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"
Обновление. Показанный здесь подход имеет серьезный недостаток: новая функция не является полностью квалифицированным членом класса;
$this
не присутствует в методе при таком вызове. Это означает, что вам придется передать объект функции в качестве параметра, если вы хотите работать с данными или функциями из экземпляра объекта! Кроме того , вы не сможете получить доступprivate
илиprotected
член класса из этих функций.
Хороший вопрос и умная идея с использованием новых анонимных функций!
Интересно, что это работает: Заменить
$me->doSomething(); // Doesn't work
с помощью call_user_func самой функции :
call_user_func($me->doSomething); // Works!
что не работает, так это «правильный» способ:
call_user_func(array($me, "doSomething")); // Doesn't work
если вызывается таким образом, PHP требует, чтобы метод был объявлен в определении класса.
Это проблема с private
/ public
/ protected
видимостью?
Обновление: Нет. Невозможно вызвать функцию обычным способом даже из класса, так что это не проблема видимости. Передача фактической функции call_user_func()
- это единственный способ, которым я могу выполнить эту работу.
вы также можете сохранять функции в массиве:
<?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);
?>
Чтобы узнать, как это сделать с помощью eval, вы можете взглянуть на мою микро-структуру PHP Halcyon, доступную на github . Он достаточно мал, чтобы вы могли без проблем разобраться - сконцентрируйтесь на классе HalcyonClassMunger.
Без __call
решения вы можете использовать bindTo
(PHP> = 5.4), чтобы вызвать метод со следующей $this
привязкой $me
:
call_user_func($me->doSomething->bindTo($me, null));
Полный сценарий может выглядеть так:
$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"
В качестве альтернативы вы можете назначить связанную функцию переменной, а затем вызвать ее без call_user_func
:
$f = $me->doSomething->bindTo($me);
$f();
Есть похожий пост о stackoverflow, в котором разъясняется, что это возможно только путем реализации определенных шаблонов проектирования.
Единственный другой способ - использовать classkit , экспериментальное расширение php. (также в посте)
Да, можно добавить метод к классу PHP после его определения. Вы хотите использовать classkit, это «экспериментальное» расширение. Однако похоже, что это расширение не включено по умолчанию, поэтому это зависит от того, можете ли вы скомпилировать собственный двоичный файл PHP или загрузить библиотеки DLL PHP, если в Windows (например, Dreamhost разрешает настраиваемые двоичные файлы PHP, и их довольно легко настроить ).
Ответ karim79 работает, однако он хранит анонимные функции внутри свойств метода, и его объявления могут перезаписывать существующие свойства с тем же именем или не работают, если существующее свойство private
вызывает непригодность объекта фатальной ошибки, я думаю, что их хранение в отдельном массиве и использование установщика является более чистым решением. Установщик инжектора методов может быть добавлен к каждому объекту автоматически с использованием трейтов .
PS Конечно, это хак, который не стоит использовать, поскольку он нарушает принцип открытости и закрытости 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);