Метод привязки 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
,
Это означает, что у вас есть два варианта решения вашей проблемы:
Bar
должен быть совместим с типомFoo
- так просто сделайBar
простираться отFoo
, если возможно.Используйте несвязанные функции, такие как анонимные, статические или функции вне классов.
Он не поддерживается 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);
Обратите внимание, что сериализация и десериализация являются дорогостоящими операциями, поэтому не используйте предоставленное решение, если нет другого решения.