2

A function or method can be called dynamically using call_user_func_array. If the call itself fails, FALSE is returned. Also, call_user_func_array returns the return values from the function or method that is called.

So when the called function or method returns FALSE as well (for example, see SO example), that value would be recognised as a false positive.

How can one reliably check if the function or method call was executed succesfully by call_user_func_array?

EDIT: People tend to point out the existence of is_callable. But this is not about checking if a method exists before calling it, thus avoiding possible errors. Actually before doing call_user_func_array the function call and it's arguments and argument types are already verified using Reflection to avoid a Massive Assign attack.

The documentation mentions the FALSE return value, but I fail to see how it can be used to check if the call was succesful.

Code4R7
  • 2,600
  • 1
  • 19
  • 42

2 Answers2

4

You can explicitly check whether an error occurred during the last call:

error_clear_last();  // since PHP 7, before that you'll need to store and
                     // compare the error state before and after the call

$result = call_user_func_array($foo, $bar);

if ($result === false && error_get_last()) {
    echo 'Failed to call ', $foo;
}

The above is a generic check for any error, perhaps you want to inspect the last error in more detail. It'll look something like:

Array
(
    [type] => 2
    [message] => call_user_func_array() expects parameter 1 to be a valid callback, function 'foo' not found or invalid function name
    [file] => /in/M8PrG
    [line] => 3
)

You might want to check that the message matches something like 'call_user_func_array() expects parameter 1 to be a valid callback' and/or that the line it refers to is the line above. Note that especially checking the message may break between PHP versions.


The alternative is to check before whether your supposed callback is_callable.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • 1
    I wonder why this was downvoted, I would think checking the last error is a more complete solution than just checking if it is callable as arguments might be missing for example. – jeroen Feb 16 '18 at 09:29
  • 2
    Quite honestly I would like to downvote PHP for necessitating this bending-over backwards; sane use of exceptions would solve this much more elegantly… ;) – deceze Feb 16 '18 at 09:31
  • Just look at the bright side, PHP offers **both** exception handling and error reporting. Consider it a convenience, just like `declare(strict_types=1);`. And while we're waiting, maybe someday we have a `declare` statement to allow multiple inheritance as well, even when it is [not really needed](http://www.stroustrup.com/bs_faq2.html#multiple). – Code4R7 Feb 17 '18 at 11:04
1

I'd transform the boolean callable into one that is void but that throws an exception on error.

That way, you could catch the exception and you would know if false was returned by the call_user_func_array that only its call failed:

<?php
$booleanCallable = function (... $args): bool {
    foreach ($args as $arg) {
        echo "$arg \n";
    };

    return false;
};

$transformBooleanCallableToVoidThrowingException = function (callable $c): callable {
    return function (... $args) use ($c): void {
        if (false === $c(... $args)) {
            throw new \RuntimeException("the call to the callable failed");
        }
    };
};

try {
    $callable = $transformBooleanCallableToVoidThrowingException($booleanCallable);
    $response = call_user_func_array($callable, [1, 2, 3]);

    if (false === $response) {
        throw new \RuntimeException("call_user_func_array failed");
    }
} catch (\Exception $e) {
    echo $e->getMessage();
}

This will output the provided arguments and an error message:

1 
2 
3 
the call to the callable failed
k0pernikus
  • 60,309
  • 67
  • 216
  • 347
  • That being said, I would avoid php functions like `call_user_func` and `call_user_func_array` unless it is *really* necessary. Most of time, you know which function should be called. – k0pernikus Feb 16 '18 at 18:15