26

I am wondering if there is a way to attach a new method to a class at runtime, in php. I mean, not on an instance level but directly to the class, so that all newly created instances, have this new method. Can such a thing be done with reflection?

Thanks

Thomas
  • 4,641
  • 13
  • 44
  • 67

5 Answers5

29

Yes, you can.

Below is the way to create method in runtime in php 5.4.x.

The anonymous function is represented by Closure class started from 5.3.x. From 5.4.x, it add a Closure::bind static method to bind the anonymous function to a particular object or class.

Example:

class Foo {
     private $methods = array();

     public function addBar() {
       $barFunc = function () {
         var_dump($this->methods);
       };
       $this->methods['bar'] = \Closure::bind($barFunc, $this, get_class());
     }

     function __call($method, $args) {
          if(is_callable($this->methods[$method]))
          {
            return call_user_func_array($this->methods[$method], $args);
          }
     }
 }

 $foo = new Foo;
 $foo->addBar();
 $foo->bar();
Tony YUEN
  • 492
  • 4
  • 4
  • 2
    Awesome. Love this new PHP features – Thomas Apr 02 '13 at 08:33
  • What is the point of __call method here, if \Closure::bind does the thing, and vice versa? – jayarjo Feb 04 '16 at 08:04
  • 2
    @jayarjo `\Closure::bind` changes the context of the anonymous function (in this case, it makes sure that, within the closure, `$this` refers to the correct object). However, it does not make that function into a method of `$this`. We still need `__call` in order to allow outside code to call the closure as if it were a method. – Ken Wayne VanderLinde Apr 15 '16 at 19:58
  • Keep in mind that this doesn't actually add methods to the object. It just makes use of `__call` and a list of methods that were added dynamically. If you run `get_class_methods` on this object your dynamic methods will not be included. – Jay Bienvenu May 07 '22 at 17:00
9

Did some playing around with whole thing. Seems that only thing you can potentially do with ReflectionClass is to replace an existing method. But even that would be indirectly.

I actually do not know any class-based language, where dynamic classes exist (then again, my knowledge is quite limited). I have seen it done only in prototype-based languages (javascript, ruby, smalltalk). Instead what you can do, in PHP 5.4, is to use Closure and add new methods to an existing object.

Here is a class which would let you perform such perversion to any object:

class Container
{
    protected $target;
    protected $className;
    protected $methods = [];

    public function __construct( $target )
    {
        $this->target = $target;
    }

    public function attach( $name, $method )
    {
        if ( !$this->className )
        {
            $this->className = get_class( $this->target );
        }
        $binded = Closure::bind( $method, $this->target, $this->className );
        $this->methods[$name] = $binded;
    }

    public function __call( $name, $arguments )
    {
        if ( array_key_exists( $name, $this->methods ) )
        {
            return call_user_func_array( $this->methods[$name] , $arguments );
        }

        if ( method_exists( $this->target, $name ) )
        {
            return call_user_func_array( 
                array( $this->target, $name ),
                $arguments
                );
        }
    }   
}

To use this, you have to provide constructor with an existing object. Here is small example of usage:

class Foo
{
    private $bar = 'payload';
};
$foobar = new Foo;
// you initial object


$instance = new Container( $foobar );
$func = function ( $param )
{
    return 'Get ' . $this->bar . ' and ' . $param;
};
$instance->attach('test', $func);
// setting up the whole thing


echo $instance->test('lorem ipsum');
// 'Get payload and lorem ipsum'

Not exactly what you want, but AFAIK this is as close you can get.

tereško
  • 58,060
  • 25
  • 98
  • 150
3

Have you taken a look at create_function() in the docs? You might also achieve the desired result by overloading.

Andrew Kozak
  • 1,631
  • 2
  • 22
  • 35
  • create_function (as fas as I know) creates functions in the global context and overloading is not as clean a solution as I would like to it to be – Thomas Jun 11 '12 at 19:18
  • I understand. PHP is not, however, as dynamic a language as would be ideal in this scenario. I believe [Reflection](http://www.php.net/manual/en/intro.reflection.php) or [Runkit](http://www.php.net/manual/en/intro.runkit.php) would be necessary to achieve a closer solution, which you alluded to in your question. – Andrew Kozak Jun 11 '12 at 19:25
  • I would love a solution with reflection, but I cannot seem to locate anywhere information. Looking at the API there is not anything that I can use – Thomas Jun 11 '12 at 19:28
  • You might want to consider [this blog post](http://mark-story.com/posts/view/using-the-php-reflection-api-for-fun-and-profit) or [this one](http://prajwal-tuladhar.net.np/2009/01/19/355/php-reflection-unleash-the-beast/) and also [this SO question](http://stackoverflow.com/questions/1593497/php-runtime-class-modification) for additional information. – Andrew Kozak Jun 11 '12 at 19:43
  • Hi Andrew. I have read all of the above prior. It seems that Reflection cannot handle my case. Runkit seems to be doing it, but it is still beta. Thanks for the help anyway – Thomas Jun 12 '12 at 09:52
  • It looks like you're stuck with either reaching outside of the basic offerings of PHP or switching languages... :c\ – Andrew Kozak Jun 12 '12 at 13:41
  • I hope future versions of PHP will add support for something like this in the Reflection API – Thomas Jun 13 '12 at 08:00
  • 1
    @Thomas , doubtfully. Since PHP is a class-bases, not prototype-based language. – tereško Jun 14 '12 at 19:12
3

This is possible with the runkit extension's runkit_method_add(). Be careful using this in production though.

Example:

<?php
class Example {}

$e = new Example();

runkit_method_add(
    'Example',
    'add',
    '$num1, $num2',
    'return $num1 + $num2;',
    RUNKIT_ACC_PUBLIC
);

echo $e->add(12, 4);
dave1010
  • 15,135
  • 7
  • 67
  • 64
2

You can use one of the below two methods also.

function method1()
{
    echo "In method one.";
}

function method2()
{
    echo "In method two.";
}

class DynamicClass
{
    function __construct(){
        $function_names = ['method1'];
        foreach ($function_names as $function_name) {
            if (function_exists($function_name)) {
                $this->addMethod($function_name);
            }
        }
    }

    function addMethod($name)
    {
        $this->{$name} = Closure::fromCallable($name);
    }

    public function __call($name, $arguments)
    {
        return call_user_func($this->{$name}, $arguments);
    }
}


$obj = new DynamicClass();
//Call method1 added in constructor
$obj->method1();
//Add method
$obj->addMethod('method2');
$obj->method2();
Debashis Prusty
  • 393
  • 2
  • 6