我希望能够直接调用分配给对象属性的闭包,而无需将闭包重新分配给变量然后再调用它。这可能吗?
以下代码无法正常工作并导致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();
Answers:
从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
无限循环。
您可以通过在闭包上调用__invoke来完成此操作,因为这是对象用来表现功能的神奇方法:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback->__invoke();
当然,如果回调是数组或字符串(在PHP中也可以是有效的回调),那将不起作用-仅适用于闭包和其他具有__invoke行为的对象。
call_user_func($obj->callback)
那还不错。
call_user_func
可以使用字符串,而且并不总是很方便
$obj->callback->__invoke();
当人们期望它成为现实时没有必要做$obj->callback()
。这只是一致性的问题。
$obj->callback instanceof \Closure
。
$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) && $_();
每种方式都有其优点和缺点,但是最根本的解决方案仍然是戈登提出的解决方案。
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();
我知道这很旧,但是如果您使用的是PHP 5.4+,我认为Traits可以很好地解决此问题
首先,创建一个使属性可调用的特征:
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
更新:
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
PHP> = 7:
($obj->callback)();
PHP> = 5.4:
$callback = $obj->callback;
$callback();
Call to undefined method AnyObject::callback()
(当然,存在AnyObject类。)
这是基于已接受答案的另一种选择,但直接扩展了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
虽然您可能会更好。
如果您使用的是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"