'PHP binding method to another class

Can i bind method of class Foo to class Bar? And why the code below throws a warning "Cannot bind method Foo::say() to object of class Bar"? With function instead of method code works fine.

P.S. I know about extending) it is not practical question, just want to know is it real to bind non-static method to another class

class Foo {

    public $text = 'Hello World!';

    public function say() {
        echo $this->text;
    }

}

class Bar {

    public $text = 'Bye World!';

    public function __call($name, $arguments) {
        $test = Closure::fromCallable(array(new Foo, 'say'));
        $res = Closure::bind($test, $this);
        return $res();
    }

}

$bar = new Bar();
$bar->say();

Code below works fine

 function say(){
    echo $this->text;
 }
 class Bar {

    public $text = 'Bye World!';

    public function __call($name, $arguments) {
        $test = Closure::fromCallable('say');
        $res = Closure::bind($test, $this);
        return $res();
    }

}

$bar = new Bar();
$bar->say();


Solution 1:[1]

It is not supported by PHP. However in PHP 7.0 it was kinda possible. Consider this example:

class Foo {
    private $baz = 1;
    
    public function baz() {
        var_dump('Foo');
        var_dump($this->baz);
    }
}


class Bar {
    public $baz = 2;
    
    public function baz() {
        var_dump('Bar');
        var_dump($this->baz);
    }
}


$fooClass = new ReflectionClass('Foo');
$method = $fooClass->getMethod('baz');

$foo = new Foo;
$bar = new Bar;

$closure = $method->getClosure($foo);

$closure2 = $closure->bindTo($bar);

$closure2();

The output of foregoing code is:

string(3) "Foo"
int(2)

And this means method Foo::baz was called on object $bar and has accessed its $baz property rather than Foo::$baz.

Also, please note that Bar::$baz is public property. If it was private, php would throw Fatal error cannot access private property. This could've be fixed up by changing the scope of closure $closure->bindTo($bar, $bar);, however doing this was already prohibited in php7.0 and would lead to a following warning: Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure().

There's workaround however, which will work for latest versions of php. Laravel created a splendid package called laravel/serializable-closure. What it does is simple - it reads source code of closure in order to serialize it and be able to unserialize it later with all necessary context.

So basically the functionality of unserialized closure remains the same, however it is different from PHP's perspective. PHP doesn't know it was created from class method and therefore allows to bind any $this.

Final variant will look like this:

$callback = [$foo, 'baz'];

$closure = unserialize(
    serialize(new SerializableClosure(Closure::fromCallable($callback)))
)->getClosure();

$closure->call($bar);

Please, note that serialization and unserialization are expensive operations, so do not use provided solution unless there's no other solution.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1