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

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

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

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

Ответ 1

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

Ответ 2

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

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

$myObject->myFunction();

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

Ответ 3

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

Ответ 4

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

Ответ 5

Вот простой класс, который позволяет установить анонимную функцию. Это оптимизированный класс 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());
?>

Ответ 6

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

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

Ответ 7

Без решения __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(); 

Ответ 8

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

Ответ 9

Там fooobar.com/questions/76994/..., который очищает, что это возможно только при реализации определенных шаблонов проектирования.

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

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

Ответ 10

Я бы никогда не использовал его, синтаксис ужасен, но вы можете добавить объект, который имеет функции для свойства. Статически, когда возможно сохранить ввод нового и ();

class SomeFuncs{  static function a(){ /* do something */ }}

$o = new stdClass();
$o->f = SomeFuncs;
$o->f::a();

Ответ 11

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