0

I have a couple of libraries that use code similar to the following one.

$args = array_merge(array(&$target, $context), $args);
$result = call_user_func_array($callback, $args);

The code is different in both the cases, but the code I shown is what essentially is done. The $callback function uses the following signature:

function callback(&$target, $context);

Both the libraries document that, and third-party code (call it plug-in, or extension) adopts that function signature, which means none of the extensions defines the callback as, e.g., function my_extension_loader_callback($target, $context).

What confuses me are the following sentence in the documentation for call_user_func_array().

Before PHP 5.4, referenced variables in param_arr are passed to the function by reference, regardless of whether the function expects the respective parameter to be passed by reference. This form of call-time pass by reference does not emit a deprecation notice, but it is nonetheless deprecated, and has been removed in PHP 5.4. Furthermore, this does not apply to internal functions, for which the function signature is honored. Passing by value when the function expects a parameter by reference results in a warning and having call_user_func() return FALSE.

In particular, the highlighted sentence seems to suggest that is not done for functions define in PHP code.

Does using call_user_func_array() in this way work in PHP 5.4?

apaderno
  • 28,547
  • 16
  • 75
  • 90

3 Answers3

3

When using call_user_func_array, passing by value when a function expects a reference is considered an error, in newer versions of PHP.

This was valid PHP code before PHP 5.3.3:

//first param is pass by reference:
my_function(&$strName){
}

//passing by value, not by reference, is now incorrect if passing by reference is expected:
call_user_func_array("my_function", array($strSomething));

//correct usage
call_user_func_array("my_function", array(&$strSomething));

The above pass by value is no longer possible without a warning (my project is also set to throw exceptions on any kind of error (notice, warning, etc).) so I had to fix this.

Solution I've hit this problem and this is how I solved it (I have a small RPC server, so there is no such thing as referenced values after deserializing params):

//generic utility function for this kind of situations
function &array_make_references(&$arrSomething)
{ 
    $arrAllValuesReferencesToOriginalValues=array();
    foreach($arrSomething as $mxKey=>&$mxValue)
        $arrAllValuesReferencesToOriginalValues[$mxKey]=&$mxValue;
    return $arrAllValuesReferencesToOriginalValues;
}

Although $strSomething is not passed by reference, array_make_references will make it a reference to itself:

call_user_func_array("my_function", array_make_references(array($strSomething)));

I think the PHP guys were thinking of helping people catch incorrectly called functions (a well concealed pitfall), which happens often when going through call_user_func_array.

oxygen
  • 5,891
  • 6
  • 37
  • 69
1

If call_user_func_array() returns false you have a problem, otherwise everything should be fine.

Parameters aren't passed by reference by default anymore, but you do it explicitly. The only trouble could be that your reference gets lost during array_merge(), haven't tested that.

line-o
  • 1,885
  • 3
  • 16
  • 33
  • 4
    `call_user_func_array()` returns whatever the called function returns. If the called function also returns `false` as a valid return value, then relying on this returned value for error detection is not good practice. – oxygen Jul 01 '12 at 13:16
-1

I've found this same problem when upgrading to PHP5.4 when there were several sites using call_user_func_array with arguments passed by reference.

The workaround I've made is very simple and consists on replacing the call_user_func_array itself with the full function call using eval(). It's not the most elegant solution but it fits the purpose for me :)

Here's the old code:

call_user_func_array($target, &$arguments);

Which I replace with:

$my_arguments = '';
for ($i=0; $i<count($arguments); $i++) {
    if ($i > 0) { $my_arguments.= ", "; }
    $my_arguments.= "\$arguments[$i]";
}
$evalthis = " $target ( $my_arguments );";
eval($evalthis);

Hope this helps!