Метод привязки PHP к другому классу

Могу ли я связать метод класса Foo с классом Bar? И почему код ниже выдает предупреждение "Невозможно привязать метод Foo::say() к объекту класса Bar"? С функцией вместо кода метода работает нормально.

PS Я знаю о расширении) это не практический вопрос, просто хочу знать, реально ли связывать нестатический метод с другим классом

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();

Код ниже работает отлично

 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();

2 ответа

Решение

В настоящее время это не поддерживается. Если вы хотите привязать замыкание к новому объекту, оно не должно быть ложным замыканием, или новый объект должен быть совместим со старым ( исходным).

Итак, что же такое поддельное закрытие?Поддельное закрытие - это закрытие, созданное из Closure::fromCallable,

Это означает, что у вас есть два варианта решения вашей проблемы:

  1. Bar должен быть совместим с типом Foo - так просто сделай Barпростираться от Foo, если возможно.

  2. Используйте несвязанные функции, такие как анонимные, статические или функции вне классов.

Он не поддерживается PHP. Однако в PHP 7.0 это было возможно. Рассмотрим этот пример:

      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();

Вывод предыдущего кода:

      string(3) "Foo"
int(2)

А это значит метод Foo::bazбыл вызван на объект $barи получил доступ к своему $bazимущество, а не Foo::$baz.

Также обратите внимание, что Bar::$bazявляется общественным достоянием. Если бы он был закрытым, php выдал бы фатальную ошибку, не может получить доступ к частной собственности. Это можно было бы исправить, изменив область закрытия $closure->bindTo($bar, $bar);, однако это уже было запрещено в php7.0 и привело бы к следующему предупреждению: Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure().

Однако есть обходной путь , который будет работать для последних версий php. Laravel создал великолепный пакет под названием laravel/serializable-closure. То, что он делает, просто - он читает исходный код закрытия, чтобы сериализовать его и иметь возможность десериализовать его позже со всем необходимым контекстом.

Таким образом, в основном функциональность несериализованного замыкания остается прежней, однако с точки зрения PHP она отличается. PHP не знает, что он был создан из метода класса, и поэтому позволяет связать любой $this.

Окончательный вариант будет выглядеть так:

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

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

$closure->call($bar);

Обратите внимание, что сериализация и десериализация являются дорогостоящими операциями, поэтому не используйте предоставленное решение, если нет другого решения.

Другие вопросы по тегам