Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callbacks as part of a static member array

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?

like image 804
Austin Hyde Avatar asked Jun 21 '10 01:06

Austin Hyde


2 Answers

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.

like image 59
Artefacto Avatar answered Nov 08 '22 04:11

Artefacto


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.

like image 2
Brenton Alker Avatar answered Nov 08 '22 06:11

Brenton Alker