Обратные вызовы как часть статического массива членов - PullRequest
5 голосов
/ 21 июня 2010

Недавно в проекте, над которым я работаю, мне нужно было хранить обратные вызовы в массиве статических членов, например:

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 голосов
/ 21 июня 2010

Вы не можете вызвать обратные вызовы, добавив (). Это работает только в PHP 5.3 с лямбда-функциями и объектами, которые реализуют магию __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'] была лямбда-выражением:

<?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

Никогда не происходит выборка статического члена (кроме назначения лямбды). Фактически, PHP компилирует переменную $_callbacks, которая оказывается не существующей во время выполнения; отсюда твоя ошибка. Я допускаю, что это, возможно, не ошибка, но, по крайней мере, угловой случай парсера. Сначала он оценивает часть $_callbacks['foo'], а затем пытается вызвать статическую функцию, имя которой является результатом этой оценки.

В сумме - придерживайтесь call_user_func или forward_static_call.

2 голосов
/ 21 июня 2010

Поскольку в PHP функции на самом деле не являются типом данных, так как они в более функциональных языках (поэтому они указаны в относительно неудобном синтаксисе массива - даже create_function () возвращает только сгенерированное имя функция, а не указатель на функцию). Итак, call_user_func и его братья - это обходные пути (хаки?), Позволяющие использовать некоторую форму функции / способности обратного вызова.

0 голосов
/ 21 июня 2010

Если вы используете 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-код не может быть быстрее, чем C-код, используемый для реализации расширений PHP.

...