How to add a new method to a php object on the fly?

PhpMethods

Php Problem Overview


How do I add a new method to an object "on the fly"?

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

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

Php Solutions


Solution 1 - Php

You can harness __call for this:

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

Solution 2 - Php

With PHP 7 you can use anonymous classes, which eliminates the stdClass limitation.

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

$myObject->myFunction();

PHP RFC: Anonymous Classes

Solution 3 - Php

Using simply __call in order to allow adding new methods at runtime has the major drawback that those methods cannot use the $this instance reference. Everything work great, till the added methods don't use $this in the code.

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

In order to solve this problem you can rewrite the pattern in this way

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

In this way you can add new methods that can use the instance reference

$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"

Solution 4 - Php

> Update: The approach shown here has a major shortcoming: The new function is not a fully qualified member of the class; $this is not present in the method when invoked this way. This means that you would have to pass the object to the function as a parameter if you want to work with data or functions from the object instance! Also, you will not be able to access private or protected members of the class from these functions.

Good question and clever idea using the new anonymous functions!

Interestingly, this works: Replace

$me->doSomething();    // Doesn't work

by call_user_func on the function itself:

call_user_func($me->doSomething);    // Works!

what doesn't work is the "right" way:

call_user_func(array($me, "doSomething"));   // Doesn't work

if called that way, PHP requires the method to be declared in the class definition.

Is this a private / public / protected visibility issue?

Update: Nope. It's impossible to call the function the normal way even from within the class, so this is not a visibility issue. Passing the actual function to call_user_func() is the only way I can seem to make this work.

Solution 5 - Php

you can also save functions in an array:

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

Solution 6 - Php

To see how to do this with eval, you can take a look at my PHP micro-framework, Halcyon, which is available on github. It's small enough that you should be able to figure it out without any problems - concentrate on the HalcyonClassMunger class.

Solution 7 - Php

Without the __call solution, you can use bindTo (PHP >= 5.4), to call the method with $this bound to $me like this:

call_user_func($me->doSomething->bindTo($me, null)); 

The complete script could look like this:

$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"

Alternatively, you could assign the bound function to a variable, and then call it without call_user_func:

$f = $me->doSomething->bindTo($me);
$f(); 

Solution 8 - Php

This worked for me:

$obj = new stdClass();
$obj->test = function(){
    return 'Hi!';
};

return ($obj->test)();

Solution 9 - Php

There's a similar post on stackoverflow that clears out that this is only achievable through the implementation of certain design patterns.

The only other way is through the use of classkit, an experimental php extension. (also in the post)

> Yes it is possible to add a method to > a PHP class after it is defined. You > want to use classkit, which is an > "experimental" extension. It appears > that this extension isn't enabled by > default however, so it depends on if > you can compile a custom PHP binary or > load PHP DLLs if on windows (for > instance Dreamhost does allow custom > PHP binaries, and they're pretty easy > to setup).

Solution 10 - Php

karim79 answer works however it stores anonymous functions inside method properties and his declarations can overwrite existing properties of same name or does not work if existing property is private causing fatal error object unusable i think storing them in separate array and using setter is cleaner solution. method injector setter can be added to every object automatically using traits.

P.S. Of course this is hack that should not be used as it violates open closed principle of 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);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMadBadView Question on Stackoverflow
Solution 1 - Phpkarim79View Answer on Stackoverflow
Solution 2 - PhpMohamed RamramiView Answer on Stackoverflow
Solution 3 - PhpalexroatView Answer on Stackoverflow
Solution 4 - PhpPekkaView Answer on Stackoverflow
Solution 5 - PhpmiglioView Answer on Stackoverflow
Solution 6 - PhpivansView Answer on Stackoverflow
Solution 7 - PhptrincotView Answer on Stackoverflow
Solution 8 - PhpPETER NADASKYView Answer on Stackoverflow
Solution 9 - PhpZilverdistelView Answer on Stackoverflow
Solution 10 - PhpRoman ToasovView Answer on Stackoverflow