Закрытие вызова, присвоенное объекту напрямую
Я хотел бы иметь возможность вызвать замыкание, которое я назначаю прямому объекту, не переназначая замыкание переменной, а затем вызываю ее. Возможно ли это?
Код ниже не работает и вызывает Fatal error: Call to undefined method stdClass::callback()
.
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback();
Ответы
Ответ 1
Как и в случае с PHP7, вы можете сделать
$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');
или используйте Closure:: call(), хотя это не работает на StdClass
.
Перед PHP7 вам необходимо реализовать метод magic __call
, чтобы перехватить вызов и вызвать обратный вызов (что невозможно для StdClass
, конечно, потому что вы не можете добавить метод __call
)
class Foo
{
public function __call($method, $args)
{
if(is_callable(array($this, $method))) {
return call_user_func_array($this->$method, $args);
}
// else throw exception
}
}
$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');
Заметьте, что вы не можете сделать
return call_user_func_array(array($this, $method), $args);
в теле __call
, потому что это вызовет __call
в бесконечном цикле.
Ответ 2
Вы можете сделать это, вызвав __invoke при закрытии, так как тот магический метод, который используют объекты, чтобы вести себя как функции:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback->__invoke();
Конечно, это не сработает, если обратный вызов является массивом или строкой (которая также может быть действительной обратными вызовами в PHP) - только для замыканий и других объектов с поведением __invoke.
Ответ 3
По PHP 7 вы можете сделать следующее:
($obj->callback)();
Ответ 4
Кажется, возможно использование call_user_func()
.
call_user_func($obj->callback);
не изящный, хотя.... То, что говорит @Gordon, возможно, единственный способ пойти.
Ответ 5
Хорошо, если вы действительно настаиваете. Другим обходным решением будет следующее:
$obj = new ArrayObject(array(),2);
$obj->callback = function() {
print "HelloWorld!";
};
$obj['callback']();
Но это не самый красивый синтаксис.
Однако парсер PHP всегда рассматривает T_OBJECT_OPERATOR
, IDENTIFIER
, (
как вызов метода. Кажется, нет обходного пути для создания ->
обхода таблицы методов и доступа к атрибутам.
Ответ 6
Я знаю, что это старо, но я думаю, что черты прекрасно справляются с этой проблемой, если вы используете PHP 5.4 +
Сначала создайте свойство, которое делает свойства вызываемыми:
trait CallableProperty {
public function __call($method, $args) {
if (property_exists($this, $method) && is_callable($this->$method)) {
return call_user_func_array($this->$method, $args);
}
}
}
Затем вы можете использовать эту черту в своих классах:
class CallableStdClass extends stdClass {
use CallableProperty;
}
Теперь вы можете определить свойства через анонимные функции и вызвать их напрямую:
$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
Ответ 7
Так как PHP 7 закрытие можно вызвать с помощью call()
:
$obj->callback->call($obj);
Так как PHP 7 можно выполнять операции с произвольными выражениями (...)
(как объясняется Korikulum):
($obj->callback)();
Другими распространенными подходами PHP 5 являются:
-
с использованием магического метода __invoke()
(как объясняется Brilliand)
$obj->callback->__invoke();
-
с помощью функции call_user_func()
call_user_func($obj->callback);
-
с использованием промежуточной переменной в выражении
($_ = $obj->callback) && $_();
Каждый способ имеет свои плюсы и минусы, но самым радикальным и окончательным решением по-прежнему остается тот, который представлен Gordon.
class stdKlass
{
public function __call($method, $arguments)
{
// is_callable([$this, $method])
// returns always true when __call() is defined.
// is_callable($this->$method)
// triggers a "PHP Notice: Undefined property" in case of missing property.
if (isset($this->$method) && is_callable($this->$method)) {
return call_user_func($this->$method, ...$arguments);
}
// throw exception
}
}
$obj = new stdKlass();
$obj->callback = function() { print "HelloWorld!"; };
$obj->callback();
Ответ 8
Нужно подчеркнуть, что сохранение замыкания в переменной и вызов varible на самом деле (быстрее) быстрее, в зависимости от суммы вызова, становится довольно много, с xdebug (так очень точное измерение), мы говорят о 1,5 (коэффициент, используя varible, вместо прямого вызова __invoke, поэтому вместо этого просто сохраните закрытие в varible и назовите его.
Ответ 9
Обновлено:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
PHP> = 7:
($obj->callback)();
PHP> = 5.4:
$callback = $obj->callback;
$callback();
Ответ 10
Здесь другая альтернатива, основанная на принятом ответе, но прямо распространяющая stdClass:
class stdClassExt extends stdClass {
public function __call($method, $args)
{
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
Пример использования:
$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();
Вероятно, вам лучше использовать call_user_func
или __invoke
.
Ответ 11
Если вы используете PHP 5.4 или выше, вы можете привязать вызываемый к области вашего объекта вызов вызываемого пользовательского поведения. Так, например, если бы у вас была следующая настройка..
function run_method($object, Closure $method)
{
$prop = uniqid();
$object->$prop = \Closure::bind($method, $object, $object);
$object->$prop->__invoke();
unset($object->$prop);
}
И вы работали в таком классе.
class Foo
{
private $value;
public function getValue()
{
return $this->value;
}
}
Вы можете запустить свою собственную логику, как если бы вы работали из области вашего объекта
$foo = new Foo();
run_method($foo, function(){
$this->value = 'something else';
});
echo $foo->getValue(); // prints "something else"