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

10

110

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()
Constituent answered 30/5, 2010 at 8:49 Comment(2)
Is this specifically without using a class?Heuer
You could use __call. But please don't use __call. Dynamically changing an object's behaviour is a very easy way to make your code unreadable and unmaintainable.Stifle
S
102

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();
Sihonn answered 30/5, 2010 at 9:0 Comment(14)
Very, very clever but kludgy in a real world project IMO! (and +1 to counter anonymous downvoter)Marder
@pekka - but how, exactly, is it kludgy? Sure, I'm not saying I'm an expert at extending an object during runtime in PHP, but I honestly can't say I see much wrong with it. (maybe I just have poor taste)Sihonn
@karim It's just the general feeling that it's not what __call was intended for, and it might be in use otherwise already. I would feel uneasy using this from a coding style point of view. On the other hand, it is elegant, can be extended with error checks (like when $method doesn't exist) and could be defined in some core class somewhere and then passed on to all objects.Marder
@Marder - I actually agree with you now that I think about it. From what I can see, it is kludgy due to its reliance on a method whose sole purpose is to get 'triggered when invoking inaccessible methods in an object context.'. From that perspective, I suppose it is kludgy.Sihonn
@karim yup, but I suppose depending on the situation, it may be justifiable to use and better than the alternatives. However as I just discovered (see my update), both our approaches have a lot of side effects because the function added that way is not a fully qualified member of the class. (Try using $this in it.) This is making me think it's best not to use this at all at this moment in PHP - maybe PHP 6 adds this natively.Marder
I would add a is_callable check in that if as well (So you don't accidentally try to call a string). And also throw a BadMethodCallException() if you can't find a method, so you don't have it return and think it did return successfully. Also, make the first param of the function the object itself, and then do an array_unshift($args, $this); before the method call, so that the function gets a reference to the object without explicitly needing to bind it...Joijoice
I think people are missing the point, the original requirement was using a classless object.Heuer
I would actually suggest that you completely remove the isset() test because if that function is not defined, you probably do not want to return silently.Ramsay
Before PHP 5.6 you should use PECL runkit : php.net/manual/en/function.runkit-method-add.phpHathorn
This is a really clever solution. It's also a really horrible thing to do in production code that some poor sod has to maintain so please don't do it!Stifle
There is nothing wrong with this code. Not in production, not anywhere else. The only thing that could possibly be wrong is the implementation and the oversight of prospective follies. It's something that could potentially be a "gotcha" for junior devs, so some clear documentation would be necessary. Otherwise, absolutely nothing is bad about this.Mara
Also speaking in regards to using $this - of course you can't use $this in reference to the object you're dynamically creating! Why would you be able to? $this will reference the parent object of the closure -- that's very well known common knowledge with closures. If you want $this to reference anything else, a closure is likely not your solution. In my case, that's exactly what it should be, example: $object->testStuff->somethingCool()->testStuff->somethingElse(); -- how else could you chain those commands without $self referencing the parent object of the callback? :PMara
it is not actually that dynamic. I believe it can't be done in PHP. Because the way you are telling assume that we always have access to class definition and we can change it and add code for __call() method.Shelba
Accepted and most upvoted answer should be updated for completeness.Lazos
R
61

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

Riva answered 4/3, 2016 at 18:1 Comment(0)
C
24

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"
Calpac answered 30/4, 2014 at 23:11 Comment(0)
M
13

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.

Marder answered 30/5, 2010 at 9:0 Comment(0)
P
3

This worked for me:

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

return ($obj->test)();
Perimorph answered 4/3, 2021 at 13:47 Comment(0)
C
2

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);
?>
Copyread answered 16/7, 2015 at 21:47 Comment(0)
S
1

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.

Sartre answered 30/5, 2010 at 8:57 Comment(0)
J
1

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(); 
Juice answered 3/1, 2017 at 14:17 Comment(0)
F
0

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

Feckless answered 30/5, 2010 at 9:5 Comment(0)
C
0

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);
Crippling answered 23/9, 2017 at 2:27 Comment(1)
If it’s a hack that shouldn’t be used, it shouldn’t be posted as an answer.Awake

© 2022 - 2024 — McMap. All rights reserved.