作为静态成员数组的回调函数

5

最近我在一个项目中需要将回调函数存储在静态成员数组中,如下所示:

class Example {
    private static $_callbacks = array(
        'foo'=>array('Example','do_foo'),
        'bar'=>array('Example','do_bar')
    );
    private static function do_foo() { }
    private static function do_bar() { }
}

为了调用它们,我尝试了显而易见的(甚至是幼稚的)语法(在 Example 类内部):

public static function do_callbacks() {
    self::$_callbacks['foo']();
    self::$_callbacks['bar']();
}

令我惊讶的是,这并没有起作用,导致出现一个通知,告诉我正在访问未定义的变量,并且出现了一个致命错误,指出需要可调用 self::$_callbacks['foo']
然后,我尝试使用call_user_func
public static function do_callbacks() {
    call_user_func(self::$_callbacks['foo']);
    call_user_func(self::$_callbacks['bar']);
}

它起作用了!

我的问题是:

为什么我需要使用call_user_func作为中介,而不直接调用它们?

3个回答

4

您不能通过添加()来调用回调函数。这仅适用于使用lambda函数和实现__invoke魔术方法的对象的PHP 5.3(另请参见内部get_closure对象处理程序)。

首先,尽管您所说的话不对,但这是行不通的:

<?php
class Example {
    private static $_callbacks;
    private static function do_foo() { echo "foo"; }
    private static function do_bar() { echo "bar"; }

    public static function do_callbacks() {
        self::$_callbacks['foo'] = array('Example','do_foo');
        self::$_callbacks['bar'] = array('Example','do_bar');

        self::$_callbacks['foo']();
        self::$_callbacks['bar']();
    }
}
Example::do_callbacks();

但是如果self::$_callbacks ['foo']是一个lambda函数,它甚至无法正常工作:
<?php
class Example {
    private static $_callbacks;

    public static function do_callbacks() {
        self::$_callbacks['foo'] = function () { echo "foo"; };

        self::$_callbacks['foo']();
    }
}

Example::do_callbacks();

原因是解析器。上述代码编译为:
Class Example:
Function do_callbacks:
(...)
number of ops:  16
compiled vars:  !0 = $_callbacks
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   6     0  >   EXT_NOP                                                  
   7     1      EXT_STMT                                                 
         2      ZEND_FETCH_CLASS                                         
         3      ZEND_DECLARE_LAMBDA_FUNCTION                             '%00%7Bclosure%7D%2Ftmp%2Fcp9aicow0xb7fcd09b'
         4      FETCH_W                      static member       $2      '_callbacks'
         5      ZEND_ASSIGN_DIM                                          $2, 'foo'
         6      ZEND_OP_DATA                                             ~3, $4
   9     7      EXT_STMT                                                 
         8      FETCH_DIM_R                                      $5      !0, 'foo'
         9      ZEND_FETCH_CLASS                                         
        10      ZEND_INIT_STATIC_METHOD_CALL                             $6, $5
        11      EXT_FCALL_BEGIN                                          
        12      DO_FCALL_BY_NAME                              0          
        13      EXT_FCALL_END                                            
  10    14      EXT_STMT                                                 
        15    > RETURN                                                   null
实际上从未获取静态成员(除了赋值lambda之外)。事实上,PHP编译一个名为$_callbacks的变量,在运行时并不存在;因此出现了错误。我承认这可能不是一个错误,但至少是解析器的一个特例。它首先评估$_callbacks['foo']部分,然后尝试调用由该评估结果得出的静态函数名称。
总之,还是使用call_user_funcforward_static_call

哦,哇,你说得对:那不起作用!这真的很奇怪,因为当我最初试图弄清楚为什么我的原始代码不起作用时,我尝试了那个方法,它确实起作用。那我就把那个从我的问题中拿掉吧。 - Austin Hyde

2
因为在PHP中,函数不像在更多的函数式语言中那样是真正的数据类型(这就是为什么它们被指定在相对笨拙的数组语法中的原因 - 甚至create_function()只返回函数生成的名称,而不是函数指针)。因此,call_user_func及其类似函数是一种解决方法(或者说是一种把戏?),以允许某种形式的函数/回调传递能力。

这是一个原因。另一个原因是脚本的解析方式不是他所想象的那样。 - Artefacto

0
如果您使用的是PHP 5.3,并且回调函数始终是一个类的静态函数,则可以使用此代码:
public static function do_callbacks() {

  // Those can be defined in another function, or in the constructor.
  // self::$_callbacks['foo'] = array('Example','do_foo');
  // self::$_callbacks['bar'] = array('Example','do_bar');

  $class = self::$_callbacks['foo'][0];
  $method = self::$_callbacks['foo'][1];
  $class::$method();

  $class = self::$_callbacks['bar'][0];
  $method = self::$_callbacks['bar'][1];
}

这样,您就不需要使用call_user_func()了。 如果回调函数始终是相同类型的(在这种情况下,它们是一个类的静态方法,该类的名称作为数组的第一项给出),那么使用此方法是有意义的。如果您需要考虑所有可能的回调类型call_user_func()能够处理,那么最好使用call_user_func(); PHP代码无法比用于实现PHP扩展的C代码更快。


是的,我考虑过这个,但我确实需要支持任何is_callable()的东西。 - Austin Hyde

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