Недавно в проекте я продолжаю работать, я должен был сохранить обратные вызовы в статическом членском массиве, как так:
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
как посредник, и не непосредственно называют их?
Вы не можете вызывать обратные вызовы, добавляя ()
. Это работает только в 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
.
Если вы используете 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.
Потому что в PHP функции не являются типами данных, как в более функциональных языках (именно поэтому они задаются в относительно неудобном синтаксисе массива - даже create_function() возвращает только сгенерированное имя функции, а не указатель на функцию). Итак, call_user_func и его собратья являются обходными путями (хаками?), чтобы позволить некоторую форму передачи функций/обратных вызовов.