Как добавить новый метод к объекту "на лету"?
$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();
С 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
by 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()
является единственным способом, с помощью которого я могу сделать эту работу.
Вот простой класс, который позволяет установить анонимную функцию. Это оптимизированный класс https://gist.github.com/nickunderscoremok/5857846
<?php
class stdObject {
public function __construct(array $arguments = array()) {
if (!empty($arguments)) {
foreach ($arguments as $property => $argument) {
if ($argument instanceOf Closure) {
$this->{$property} = $argument;
} else {
$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);
?>
Без решения __call
вы можете использовать bindTo
(PHP >= 5.4), чтобы вызвать метод с $this
> связанный с $me
следующим образом:
call_user_func($me->doSomething->bindTo($me, null));
Полный script может выглядеть так:
$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();
Чтобы узнать, как это сделать с помощью eval, вы можете взглянуть на мою микроструктуру PHP, Halcyon, которая доступна на github. Он достаточно мал, чтобы вы могли понять его без каких-либо проблем - сосредоточьтесь на классе HalcyonClassMunger.
Там fooobar.com/questions/76994/..., который очищает, что это возможно только при реализации определенных шаблонов проектирования.
Единственный способ - использовать classkit, экспериментальное расширение php. (также в сообщении)
Да, можно добавить метод класс PHP после его определения. Вы хотите использовать classkit, который является "экспериментальное" расширение. Кажется что это расширение не включено по умолчанию, так что это зависит от того, если вы можете скомпилировать собственный двоичный файл PHP или загружать библиотеки PHP, если на окнах (для экземпляр Dreamhost позволяет PHP-двоичные файлы, и они довольно легкие для настройки).
Я бы никогда не использовал его, синтаксис ужасен, но вы можете добавить объект, который имеет функции для свойства. Статически, когда возможно сохранить ввод нового и ();
class SomeFuncs{ static function a(){ /* do something */ }}
$o = new stdClass();
$o->f = SomeFuncs;
$o->f::a();
karim79 отвечает, однако он хранит анонимные функции внутри свойств метода, и его декларации могут перезаписывать существующие свойства с тем же именем или не работают, если существующее свойство private
приводит к тому, что фатальный объект ошибки неприменим. Я думаю, что хранение их в отдельном массиве, а использование сеттера - более чистое решение. метод установки инжектора может быть добавлен к каждому объекту автоматически, используя traits.
P.S. Конечно, это взлом, который нельзя использовать, поскольку он нарушает открытый закрытый принцип 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);