我想直接调用分配给对象属性的闭包函数,而不必重新分配到变量再调用。这可能吗?
下面的代码无法工作,并导致“Fatal error: Call to undefined method stdClass::callback()”。
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback();
我想直接调用分配给对象属性的闭包函数,而不必重新分配到变量再调用。这可能吗?
下面的代码无法工作,并导致“Fatal error: Call to undefined method stdClass::callback()”。
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback();
从PHP7开始,您可以执行以下操作
$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');
或者使用Closure::call(),不过这在StdClass
上不起作用。
在 PHP7 之前,你需要实现魔术方法 __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
。通过在闭包上调用__invoke,您可以实现这一点,因为这是对象用来像函数一样运行的神奇方法:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback->__invoke();
当然,如果回调是数组或字符串(在PHP中也可以是有效的回调函数),那么这种方法将无法正常工作 - 只适用于闭包和其他具有__invoke行为的对象。
call_user_func($obj->callback)
也不错。 - marciocall_user_func
也可以使用字符串,这并不总是方便的。 - Gherman$obj->callback()
时,不必要写成 $obj->callback->__invoke();
。这只是一个一致性的问题。 - Mahn$obj->callback instanceof \Closure
是有意义的。 - bishopcall()
方法调用闭包:$obj->callback->call($obj);
自从PHP 7发布以来,就可以对任意(...)
表达式执行操作(正如Korikulum所解释的那样):
($obj->callback)();
其他常见的PHP 5方法包括:
using the magic method __invoke()
(as explained by Brilliand)
$obj->callback->__invoke();
using the call_user_func()
function
call_user_func($obj->callback);
using an intermediate variable in an expression
($_ = $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();
好的,如果你真的坚持。另一个解决方法是:
$obj = new ArrayObject(array(),2);
$obj->callback = function() {
print "HelloWorld!";
};
$obj['callback']();
但这不是最好的语法。
然而,PHP解析器总是将T_OBJECT_OPERATOR
、IDENTIFIER
、(
视为方法调用。似乎没有绕过方法表直接访问属性的解决方法。
我知道这是旧的内容,但如果你正在使用PHP 5.4+,Traits可以很好地处理这个问题。
首先,创建一个trait,使属性可调用:
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
call_user_func()
来实现。call_user_func($obj->callback);
虽然不够优雅,但... @Gordon所说的可能是唯一可行的方法。
首先需要强调的是,将闭包存储在变量中并调用该变量实际上(奇怪地)更快,这取决于调用次数,随着调用次数的增加,使用变量而不是直接调用__invoke,因此因素增加了1.5倍左右。因此,只需将闭包存储在变量中并调用它即可。
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
。
更新:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
($obj->callback)();
$callback = $obj->callback;
$callback();
Call to undefined method AnyObject::callback()
(当然,AnyObject类是存在的)。 - Kontrollfreak