Recently in a project I am working on, I needed to store callbacks in a static member array, like so:
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() { }
}
To call them, I tried the obvious (maybe even naive) syntax (inside the Example
class):
public static function do_callbacks() {
self::$_callbacks['foo']();
self::$_callbacks['bar']();
}
To my surprise, this did not work, resulting in a notice that I was accessing an undefined variable, and a fatal error stating that self::$_callbacks['foo']
needed to be callable.
Then, I tried call_user_func
:
public static function do_callbacks() {
call_user_func(self::$_callbacks['foo']);
call_user_func(self::$_callbacks['bar']);
}
And it worked!
My question is:
Why do I need to use call_user_func
as an intermediary, and not directly call them?
You can't call callbacks by appending ()
. That only works in PHP 5.3 with lambda functions and objects that implement the __invoke
magic (see also the internal get_closure
object handler).
First, despite what you say, this doesn't work:
<?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();
But it wouldn't even work if self::$_callbacks['foo']
was a lambda:
<?php
class Example {
private static $_callbacks;
public static function do_callbacks() {
self::$_callbacks['foo'] = function () { echo "foo"; };
self::$_callbacks['foo']();
}
}
Example::do_callbacks();
The reason is the parser. The above compiles to:
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
There's never a fetching of a static member (except for the assignment of the lambda). In fact, PHP compiles a variable $_callbacks
, which turns out not to exist at runtime; hence your error. I concede this is, maybe not a bug, but at least a corner case of the parser. It evaluates the $_callbacks['foo']
part first and then attempts to call the static function whose name results from that evaluation.
In sum – stick to call_user_func
or forward_static_call
.
Because in PHP functions are not really data type as they are in more functional languages (which is why they are specified in a relatively awkward array syntax - even create_function() only returns the generated name of the function, not a function pointer). So, call_user_func and it's brethren are workarounds (hacks?) to allow some form of function/callback passing ability.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With