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


98

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

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

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

Это специально без использования класса?
thomas-peter

1
Вы можете использовать __call. Но, пожалуйста, не используйте __call. Динамическое изменение поведения объекта - очень простой способ сделать ваш код нечитаемым и не поддерживаемым.
GordonM 03

Вы можете использовать этот инструмент: packagist.org/packages/drupol/dynamicobjects
Pol Dellaiera

Ответы:


95

__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();

6
Очень, очень умно, но глупо в реальном проекте IMO! (и +1 против анонимного отрицателя)
Пекка

2
@pekka - а как именно он тупой? Конечно, я не говорю, что я эксперт по расширению объектов во время выполнения в PHP, но я, честно говоря, не могу сказать, что вижу в этом много плохого. (возможно, у меня просто плохой вкус)
karim79

16
Я бы также добавил в это if проверку is_callable (чтобы вы случайно не попытались вызвать строку). А также бросить BadMethodCallException (), если вы не можете найти метод, поэтому вы не получите его возврата и думаете, что он вернулся успешно. Кроме того, сделайте первый параметр функции самим объектом, а затем сделайте его array_unshift($args, $this);перед вызовом метода, чтобы функция получила ссылку на объект без явной необходимости его привязки ...
ircmaxell

3
Я думаю, что люди упускают из виду суть, изначальным требованием было использование бесклассового объекта.
thomas-peter

1
На самом деле я бы посоветовал вам полностью удалить тест isset (), потому что, если эта функция не определена, вы, вероятно, не захотите возвращаться молча.
Alexis Wilke


20

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

12

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

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

<?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

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


Я предполагаю (как и мой код), что вы работаете на PHP <5.3.0 и, следовательно, вам нужны кладжи и хаки для работы.
ivans

Я полагаю, что спрашивать, не хочет ли кто-нибудь прокомментировать голосование против, бессмысленно ...
ivans

Вероятно, здесь происходит массовое голосование против. Но, может быть, вы захотите немного рассказать о том, что вы сделали? Как видите, это интересный вопрос, на который пока нет однозначного ответа.
Пекка

1

Без __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

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

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

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


0

Ответ 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);

Если это взлом, который не следует использовать, его не следует публиковать в качестве ответа.
miken32 09
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.