How can I invoke a ReflectionFunction wrapping a c

2020-02-13 06:38发布

问题:

This is easiest to explain with an example:

class Example {
    private $x;
    public $f;

    public function __construct() {
        $this->x = 10;
        $this->f = function() {
            return $this->x;
        };
    }
}

$ex = new Example();
$f = new ReflectionFunction($ex->f);
echo $f->invoke().PHP_EOL;

Running this results in an error:

PHP Fatal error: Uncaught Error: Using $this when not in object context

That's because I've used $this in the closure, so it's really more like a ReflectionMethod, but ReflectionMethod doesn't seem to want to take a closure as an argument, so I'm not really sure what I can do.

How can I invoke $ex->f using reflection?

回答1:

Well, I really don't know why that behavior is happening. But there's a workaround (well, I found it after a few tests).

As PHP doesn't let you explicit the bind of $this (it's bound automatically), you have to use an alternative variable:

$t = $this;

$this->f = function() use ($t) {
    return $t->x;
};

The entire code:

class Example {
    private $x;
    public $f;

    public function __construct() {
        $this->x = 10;

        $t = $this;

        $this->f = function() use ($t) {
            return $t->x;
        };
    }
}

$ex = new Example();
$f = new ReflectionFunction($ex->f);
echo $f->invoke().PHP_EOL;

And the result wanted

10

Tested on PHP 5.4, 5.5, 5.6 and 7.

UPDATE

After @mpen answer, I've realized about his restrictions and the use of Reflection.

When you use a ReflectionFunction to invoke a function, which is a closure at least, you should treat that as a closure. ReflectionFunction has a method called ReflectionFunction::getClosure().

The Class remains as @mpen created and the use will be as:

$ex = new Example();
$f = new ReflectionFunction($ex->f);
$closure = $f->getClosure();
echo $closure().PHP_EOL;

But only works on PHP 7.

For PHP 5.4, 5.5 and 5.6 you'll have to bind the class and scope. Weird, but it's the only way that I found using Closure::bindTo() or Closure::bind():

$ex = new Example();
$f = new ReflectionFunction($ex->f);    
$closure = $f->getClosure();
$class = $f->getClosureThis();
$closure = $closure->bindTo($class , $class);
echo $closure().PHP_EOL;

Or just:

$ex = new Example();
$f = new ReflectionFunction($ex->f);    
$class = $f->getClosureThis();
$closure = Closure::bind($f->getClosure() , $class , $class);
echo $closure().PHP_EOL;

It's very important to pass the class as scope (second parameter) which will determine whether you can access private/protected variable or not.

The second paramater also could be the class name as:

$closure = $closure->bindTo($class , 'Example');//PHP >= 5.4
$closure = $closure->bindTo($class , get_class($class));//PHP >= 5.4
$closure = $closure->bindTo($class , Example::class);//PHP 5.5

But I didn't concerned about performance, so the class passed twice is just fine to me.

There's also the method Closure::call() which can be used to change the scope, but also just for PHP >= 7.