How can I invoke a ReflectionFunction wrapping a closure that utilizes $this?

后端 未结 1 879
南方客
南方客 2020-12-20 09:20

This is easiest to explain with an example:

class Example {
    private $x;
    public $f;

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


        
相关标签:
1条回答
  • 2020-12-20 10:15

    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.

    0 讨论(0)
提交回复
热议问题