直接调用分配给对象属性的闭包


109

我希望能够直接调用分配给对象属性的闭包,而无需将闭包重新分配给变量然后再调用它。这可能吗?

以下代码无法正常工作并导致Fatal error: Call to undefined method stdClass::callback()

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();

1
这正是您所需要的:github.com/ptrofimov/jslikeobject还有更多:您可以在闭包内部使用$ this并使用继承。只有PHP> = 5.4!
Renato Cuccinotto

Answers:


106

从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
有时,您会发现此语法很有用-call_user_func_array($ this-> $ property,$ args); 关于可调用类属性,而不是方法。
Nikita Gopkalo 2014年

105

您可以通过在闭包上调用__invoke来完成此操作,因为这是对象用来表现功能的神奇方法:

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback->__invoke();

当然,如果回调是数组或字符串(在PHP中也可以是有效的回调),那将不起作用-仅适用于闭包和其他具有__invoke行为的对象。


3
@marcioAlmada虽然非常丑陋。
Mahn 2014年

1
@Mahn我认为这比公认的答案更明确。在这种情况下,显式更好。如果您真的在乎“可爱”的解决方案,call_user_func($obj->callback)那还不错。
marcio

但是也call_user_func可以使用字符串,而且并不总是很方便
Gherman 2014年

2
@cerebriform从语义上讲,$obj->callback->__invoke();当人们期望它成为现实时没有必要做$obj->callback()。这只是一致性的问题。
曼恩,2015年

3
@Mahn:当然,这并不一致。但是,一致性从未真正成为PHP的强项。:)但是,当人们考虑对象的性质时,我认为这确实有意义$obj->callback instanceof \Closure
主教


10

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) && $_();

每种方式都有其优点和缺点,但是最根本的解决方案仍然是戈登提出的解决方案。

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

7

似乎可以使用call_user_func()

call_user_func($obj->callback);

不过并不优雅。...@戈登所说的可能是唯一的方法。


7

好吧,如果您真的坚持的话。另一个解决方法是:

$obj = new ArrayObject(array(),2);

$obj->callback = function() {
    print "HelloWorld!";
};

$obj['callback']();

但这不是最好的语法。

然而,PHP解析器始终将T_OBJECT_OPERATORIDENTIFIER(如方法调用。似乎没有替代方法可以->绕过方法表并访问属性。


7

我知道这很旧,但是如果您使用的是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

哇:)这比我想做的要优雅得多。我唯一的问题与英国的热/冷水龙头连接器元件相同:为什么还没有内置?
dkellner

谢谢 :)。由于其含糊不清,因此可能不是内置的。假设在我的示例中,实际上是否存在一个名为“ add”的函数和一个名为“ add”的属性。括号的存在告诉PHP查找具有该名称的函数。
SteveK

2

好吧,应该强调的是,将闭包存储在变量中,然后调用变量实际上(非常)快(取决于调用量),根据调用量,它变得很多,使用xdebug(非常精确的度量),我们正在谈论1,5(使用变量,而不是直接调用__invoke的因素。因此,只需将闭包存储在变量中并调用它。


2

更新:

$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类。)
Kontrollfreak

1

这是基于已接受答案的另一种选择,但直接扩展了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虽然您可能会更好。


0

如果您使用的是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"

0

我注意到这在PHP5.5中有效

$a = array();
$a['callback'] = function() {
    print "HelloWorld!";
};
$a['callback']();

允许创建一个闭包的伪对象集合。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.