Как добавить новый метод в объект php на лету?
Как добавить новый метод к объекту "на лету"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
11 ответов
Вы можете использовать __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();
С PHP7 вы можете использовать анонимные классы
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Использование простого __call для разрешения добавления новых методов во время выполнения имеет главный недостаток, заключающийся в том, что эти методы не могут использовать ссылку $this instance. Все отлично работает, пока добавленные методы не используют $ 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()
это единственный способ заставить меня работать.
Это сработало для меня:
$obj = new stdClass();
$obj->test = function(){
return 'Hi!';
};
return ($obj->test)();
Здесь простой класс, позволяющий установить анонимную функцию. Это оптимизированный класс https://gist.github.com/nickunderscoremok/5857846
<?php
class stdObject {
public function __construct(array $arguments = array()) {
if (!empty($arguments)) {
foreach ($arguments as $property => $argument) {
$this->{$property} = $argument;
}
}
}
public function __call($method, $arguments) {
if (isset($this->{$method}) && is_callable($this->{$method})) {
return call_user_func_array($this->{$method}, $arguments);
} else {
throw new Exception("Fatal error: Call to undefined method stdObject::{$method}()");
}
}
}
$person = new stdObject(array(
"name" => "nick",
"age" => 23,
"friends" => array("frank", "sally", "aaron"),
"sayHi" => function() {
return "Hello there";
}
));
$person->sayHi2 = function() {
return "Hello there 2";
};
$person->test = function() {
return "test";
};
var_dump($person->name, $person->test(), $person->sayHi2());
?>
Вы также можете сохранить функции в массиве:
<?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();
Ответkarim79 работает, однако он хранит анонимные функции в свойствах метода, и его объявления могут перезаписывать существующие свойства с тем же именем или не работают, если существующее свойство private
Причинение фатальной ошибки невозможно. Я думаю, что хранение их в отдельном массиве и использование setter - более чистое решение Метод установки инжектора может быть добавлен к каждому объекту автоматически, используя черты.
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);
Существует аналогичная статья о стекаповороте, которая проясняет, что это возможно только путем реализации определенных шаблонов проектирования.
Единственный другой путь - использование classkit, экспериментального расширения php. (также в посте)
Да, можно добавить метод в класс PHP после его определения. Вы хотите использовать classkit, который является "экспериментальным" расширением. Похоже, что это расширение не включено по умолчанию, поэтому оно зависит от того, можете ли вы скомпилировать пользовательский двоичный файл PHP или загрузить PHP DLL, если в Windows (например, Dreamhost допускает использование пользовательских двоичных файлов PHP, и их довольно легко настроить).