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