回调作为静态成员数组的一部分

时间:2010-06-21 01:22:46

标签: php static callback

最近在我正在开发的项目中,我需要将回调存储在静态成员数组中,如下所示:

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 个答案:

答案 0 :(得分:4)

您无法通过附加()来呼叫回叫。这只适用于PHP 5.3中的lambda函数和实现__invoke魔法的对象(另请参阅内部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,结果证明它不存在于运行时;因此你的错误。我承认这可能不是一个bug,但至少是解析器的一个极端情况。它首先评估$_callbacks['foo']部分,然后尝试调用名称来自该评估的静态函数。

总之 - 坚持call_user_funcforward_static_call

答案 1 :(得分:2)

因为PHP函数不是真正的数据类型,因为它们是更多函数式语言(这就是为什么它们以相对笨拙的数组语法指定 - 甚至create_function()只返回生成的函数名称,而不是功能指针)。那么,call_user_func和它的兄弟是解决方法(hacks?)允许某种形式的函数/回调传递能力。

答案 2 :(得分: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代码更快。