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


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

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

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

10 ответов:

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

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

использование простого _ _ вызова для добавления новых методов во время выполнения имеет главный недостаток, что эти методы не могут использовать ссылку на экземпляр $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"

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

это a 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) {
                $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)); 

полный сценарий может выглядеть так:

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

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

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

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

karim79 ответ работает, однако он хранит анонимные функции внутри свойств метода, и его объявления могут перезаписывать существующие свойства с тем же именем или не работают, если существующее свойство private вызывая фатальную ошибку объект непригоден я думаю, что хранение их в отдельном массиве и с помощью сеттера является более чистым решением. метод инжектор сеттер может быть добавлен к каждому объекту автоматически с помощью черт.

P. S. Конечно, это хак, который не должен используйте по мере того как оно нарушает открытый закрытый принцип твердого тела.

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

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