Dynamic Invocation in PHP: is_callable, call_user_func, and call_user_func_array - Functional PHP 5.3 Part III

Posted Aug 31, 2009 by Kris Jordan | Comments ( 3 ) | Filed in: PHP | | | | |

In PHP you can dynamically invoke functions, lambdas, and methods in a number of ways. In this third part of the series we're going to explore callables in PHP 5.3. Check out Part I of the series if you're unfamiliar with anonymous functions, lambdas and closures in PHP 5.3. Part II followed up with a look at higher order PHP functions by implementing map and reduce.

What is Dynamic Invocation?

We've seen a flavor of dynamic invocation in the previous posts of this series using higher-order methods. The functions map and reduce receive a lambda as an argument and dynamically invoke it at runtime. PHP doesn't know (or care) in advance which lambda it is passed and it won't know until runtime:

$lambda = rand(0,1) ? function() { echo "Heads!"; } :
                      function() { echo "Tails!"; };
$lambda(); // <= Dynamic Invocation!

It's not until we run the script and flip the coin that PHP will know which anonymous function is being invoked. PHP has actually had the ability to do dynamic invocation for quite some time. Let's take a look at a similar example using code that runs pre- PHP 5.3:

function heads() { echo "Heads!"; }
function tails() { echo "Tails!"; }
$function = rand(0,1) ? 'heads' : 'tails';
$function(); // <= Dynamic Invocation!

We start by defining two functions named heads and tails, respectively. We then flip a coin and assign the string "heads" or "tails" to $function. Finally we invoke $function as if it were a lambda and the correct function gets called! Under its breath PHP is saying "looks like a function call, but this is actually a string whose value is 'heads'. Do I know of a function named 'heads'? Yes! Initiate dynamic invocation thrusters in 5...4...3...2..."

What if $function's value was a string with no corresponding method name, or an integer, or a boolean? You would murder our friend PHP. You should know that death by dysfunctional dynamic invocation is a most cruel and unusual death.

$fatality = 'Finish Him!';
$fatality();
// Fatal error: Call to undefined function Finish Him!()

is_callable to PHP's defense

How can you stop yourself from murdering PHP with dysfunctional dynamic invocations? Enter: is_callable. If safety is your concern, is_callable is your defense. Pass any variable you want to is_callable and it will tell you true or false whether or not it can be dynamically invoked.

function heads() { echo "Heads!"; }
$function = 'heads';
is_callable($function); // true

$lambda = function() { echo "Tails!"; }
is_callable($lambda); // true

$kimJungIl = "fireNukesAndSingAboutLoneliness";
is_callable($kimJungIl); // false

With the powers of is_callable we can learn whether making a dynamic call to the contents of a variable makes sense. Notice that is_callable works on both the function name string variable $function, and the anonymous function $lambda. We can call them just the same, too, so they can be used interchangeably:

function heads() { echo "Heads!"; }
$callable = rand(0,1) ? 'heads' : function() { echo "Tails!"; };
if(is_callable($callable)) {
    $callable();
}

This is really sweet because with higher-order functions like map and reduce we can pass either a lambda as demonstrated in part II, or string that is the name of a first-class function, and it will work just the same. Dynamic invocation enables flexibility through indirection and late-binding. Now that we've seen dynamic invocations of functions and anonymous functions, what about methods and static methods?

Dynamically Invoking Instance and Static Methods

While practicing the powers of our ability cast dynamically invocations of methods and lambdas, an elder PHP programmer presents a challenge: dynamically invoke an object's method without our script becoming a statistic, just another PHP fatality. With a mischeivious grin she hints, "Try not with a string, you will. Do with a two element array, you must."

class Object { 
	function method() { echo "Great success!"; }
}
$callableArray = array(new Object, 'method');
if(is_callable($callableArray)) {
    $callableArray();
}

Taking the hint we place an instance of the object as the first element of our array, and the name of the method as the second. We protect ourselves by only commiting to the dynamic invocation if the array is callable. We're safe, right? So, we run the script... and we murder PHP. Fatal error: Function name must be a string The elder chuckles and walks away...

Use call_user_func and call_user_func_array for Robust Dynamic Invocation in PHP

PHP's achilles heel is its inconsitencies. It may never live them down. C'est la vie. It turns out is_callable doesn't mean directly callable, but rather callable using the utility functions call_user_func or call_user_func_array. Let's try that example again:

class Object { 
	function method($greatWho, $greatWhat) { 
		echo "$greatWho are Great $greatWhat!";
	}
}
$callableArray = array(new Object, 'method');
if(is_callable($callableArray)) {
    call_user_func($callableArray, "You", "Success");
    call_user_func_array($callableArray, array("You", "Success"));
}

"You are Great Success!" Says the script. Notice the subtle variation in how each is called. call_user_func takes a list of arguments following the $callable just like any other function call, whereas call_user_func _array takes an array that will be used as arguments. In practice this means you can use call_user_func when you know exactly the arguments you want to send to the callable, and call_user_func_array when you don't know how many arguments or when it varies. Can you think of a time when you wouldn't know the exact arguments to use in a dynamic invocation?

Thought of an example yet? How about if we wanted a generic way to wrap new behavior around a function? A simple example...

function wrapWithEcho($callable) {
    return function() use ($callable) {
        echo "Calling!";
        call_user_func_array($callable, func_get_args()); // !!!
        echo "Done calling!"; 
	};
}

function aDeliciousFunction($subject) { echo "Mmm, $subject!"; }
$aDeliciousFunction = 'aDeliciousFunction';
$aDeliciousFunction('candy'); 
// Mmm, Candy!
$aDeliciousFunction = wrapWithEcho($aDeliciousFunction);
$aDeliciousFunction('candy');
// Calling! Mmm, Candy! Done Calling!

function anotherDeliciousFunction($subject, $verb) {
	echo "I love to $verb $subject!";
}
$anotherDeliciousFunction = 'anotherDeliciousFunction';
$anotherDeliciousFunction('candy','eat'); 
// I love to eat candy!
$anotherDeliciousFunction = wrapWithEcho($anotherDeliciousFunction);
$anotherDeliciousFunction('candy','unwrap'); 
// Calling! I love to unwrap candy! Done calling!

The focal point of this example is near the three bangs (!!!). Our wrapWithEcho function is returning an anonymous function that closes over the callable and does not know, or care, how many arguments that the callable should be passed. In fact, we wrap two different functions that take two different parameter counts and it all just works! Without call_user_func_array, in a situation like this, we'd have to resort to a huge switch statement that counted the number of arguments and then invoked call_user_func with exactly the right count.

There is another way to dynamically invoke callables, using reflection, which will be the subject of a short follow-up post.

Calling all Callables! (Directly)

Let's have a little fun and tie together concepts from each post in the series thus far to create a uniform means for doing direct dynamic invocation. To accomplish this we'll use PHP's closures and anonymous functions (part I) to create a higher-order PHP function (part II) that will return a directly invokable value for all callables  (part III). We're going to totally unify our dynamic invocations so we don't have to use call_user_func in our code, but can instead do this:

function aFunFunction() { echo "Radical."; }
$function = callable('aFunFunction');
$function();

$lambda = callable(function() { echo "Awesome!"; });
$lambda();

class Object { 
    function method() { echo "Great success!"; }
    static function staticMethod() { echo "More great success!"; }
}
$method = callable(array(new Object, 'method'));
$method();

$staticMethod = callable(array('Object','staticMethod'));
$staticMethod();

Now we just need to write this callable method. If you think back to our wrapper example, it's got the hints we need. Our callable function will return an anonymous function that closes over the callable. Ready?

function callable($aCallable) { 
     if(!is_callable($aCallable)) { 
		throw new Exception("Callable only works on is_callable's!");
	}
    // Return our anonymous function
    return function() use ($aCallable) {
        return call_user_func_array($aCallable, func_get_args());
    };
}

Pretty awesome, but I know what you're thinking. You're right, you can do better. Remember what we started out learning in this post? There are two kinds of callables that are directly callable: strings and anonymous functions. (Aside: there are actually three, the third is an object that implements the magical __invoke method, we'll get there later in this series.) Right, strings and anonymous functions can be called directly, it's the callables in the form of an array that need call_user_func. We can make our code a lot faster by simply returning callable non-arrays because they're already directly callable!

function callable($aCallable) {
	if(!is_callable($aCallable)) { 
		throw new Exception("Callable only works on is_callable's!");
	}
    if(!is_array($aCallalbe)) return $aCallable;
    return function() use ($aCallable) {
        return call_user_func_array($aCallable, func_get_args());
    };
}

Awesome. Just awesome. Using a closure we've brought unification to direct dynamic invocation in PHP 5.3 with our shiny new callable function.

Just a simple example of the power and flexibility functional PHP 5.3 brings to the table. Are you enjoying this series? Let me know in the comments. If so, you should subscribe to our RSS feed as parts IV and V are on their way...

You can also follow along on Twitter to get updates as this series continues...

Comments

Comments are moderated. It may take some time for your comment to appear.

Recess can only be as good as the thoughts that go into it. Let us hear yours...

  1. Posted by marko on Sep 2, 2009
    Can't wait to see PHP 5.3 fully implemented in recess!
  2. Posted by Adam on Sep 2, 2009
    Typo in last example (aCallalbe):

    if(!is_array($aCallalbe)) return $aCallable;
  3. Posted by bungle on Sep 4, 2009
    For static methods you can even use:
    $f = 'NameSpaceClass::method';
    $f();

    I would love if PHP natively supports also this:
    $f = 'NameSpaceClass->method';
    $f();

    (creates a class using default constructor and then calls the instance method - well it's rather easy to write the same by yourself).

You must login to comment on this page.