Как добавить новый метод в объект php на лету? - PullRequest
88 голосов
/ 30 мая 2010

Как добавить новый метод к объекту «на лету»?

$me= new stdClass;
$me->doSomething=function ()
 {
    echo 'I\'ve done something';
 };
$me->doSomething();

//Fatal error: Call to undefined method stdClass::doSomething()

Ответы [ 9 ]

92 голосов
/ 30 мая 2010

Вы можете использовать __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();
40 голосов
/ 04 марта 2016

С PHP 7 вы можете использовать анонимные классы

$myObject = new class {
    public function myFunction(){}
};

$myObject->myFunction();

PHP RFC: анонимные классы

18 голосов
/ 01 мая 2014

Использование простого __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"
12 голосов
/ 30 мая 2010

Обновление: Показанный здесь подход имеет существенный недостаток: новая функция не является полностью квалифицированным членом класса; $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() - единственный способ заставить эту работу работать.

2 голосов
/ 17 июля 2015

Вы также можете сохранять функции в массиве:

<?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);
?>
1 голос
/ 03 января 2017

Без решения __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(); 
0 голосов
/ 23 сентября 2017

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);
0 голосов
/ 30 мая 2010

Существует аналогичный пост в stackoverflow , который проясняет, что это возможно только путем реализации определенных шаблонов проектирования.

Единственный другой способ - использование classkit , экспериментального расширения php. (также в посте)

Да, можно добавить метод к класс PHP после его определения. Вы хочу использовать classkit, который является «экспериментальное» расширение. Кажется что это расширение не включено однако по умолчанию, так что это зависит от того, если Вы можете скомпилировать пользовательский бинарный PHP или загрузить PHP DLL, если на Windows (для Экземпляр Dreamhost действительно позволяет настраивать Бинарные файлы PHP, и они довольно просты настроить).

0 голосов
/ 30 мая 2010

Чтобы увидеть, как это сделать с помощью eval, вы можете взглянуть на мою PHP-фреймворк Halcyon, который доступен на github . Он достаточно мал, чтобы вы могли без проблем разобраться в этом - сконцентрируйтесь на классе HalcyonClassMunger.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...