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

121

我想直接调用分配给对象属性的闭包函数,而不必重新分配到变量再调用。这可能吗?

下面的代码无法工作,并导致“Fatal error: Call to undefined method stdClass::callback()”。

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

1
这正是你所需要的:https://github.com/ptrofimov/jslikeobject 更棒的是:你可以在闭包内使用$this并且使用继承。仅支持PHP>=5.4! - Renato Cuccinotto
12个回答

127

从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

3
有时候你会发现这个语法很有用 - 当它涉及到可调用的类属性而不是方法时,可以使用call_user_func_array($this->$property, $args)。 - Nikita Gopkalo

112

通过在闭包上调用__invoke,您可以实现这一点,因为这是对象用来像函数一样运行的神奇方法:

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

当然,如果回调是数组或字符串(在PHP中也可以是有效的回调函数),那么这种方法将无法正常工作 - 只适用于闭包和其他具有__invoke行为的对象。


3
@marcioAlmada 非常丑陋。 - Mahn
1
@Mahn 我认为这比被接受的答案更明确。在这种情况下,明确更好。如果你真的想要一个“可爱”的解决方案,call_user_func($obj->callback)也不错。 - marcio
但是 call_user_func 也可以使用字符串,这并不总是方便的。 - Gherman
2
@cerebriform 从语义上讲,当人们期望 $obj->callback() 时,不必要写成 $obj->callback->__invoke();。这只是一个一致性的问题。 - Mahn
3
@Mahn: 承认,它不是一致的。 而且一致性从来都不是 PHP 的强项,但是我认为当考虑到对象本质时,$obj->callback instanceof \Closure 是有意义的。 - bishop

28

PHP 7 开始,您可以执行以下操作:

($obj->callback)();

如此常识,却是纯粹的坏蛋。PHP7 的强大力量! - tfont

11
自从PHP 7以后,可以使用call()方法调用闭包:
$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();

7

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

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

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

$obj['callback']();

但这不是最好的语法。

然而,PHP解析器总是将T_OBJECT_OPERATORIDENTIFIER(视为方法调用。似乎没有绕过方法表直接访问属性的解决方法。


7

我知道这是旧的内容,但如果你正在使用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

哇 :) 这比我尝试做的要优雅得多。我的唯一问题与英国热/冷水龙头连接器元素相同:为什么它不是已经内置了呢? - dkellner
谢谢 :). 这可能是因为它会产生歧义,所以没有内置。想象一下,在我的例子中,如果实际上有一个名为“add”的函数和一个名为“add”的属性。括号的存在告诉PHP查找具有该名称的函数。 - SteveK

7
似乎可以使用call_user_func()来实现。
call_user_func($obj->callback);

虽然不够优雅,但... @Gordon所说的可能是唯一可行的方法。


2

首先需要强调的是,将闭包存储在变量中并调用该变量实际上(奇怪地)更快,这取决于调用次数,随着调用次数的增加,使用变量而不是直接调用__invoke,因此因素增加了1.5倍左右。因此,只需将闭包存储在变量中并调用它即可。


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


1

更新:

$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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接