Callback-functions explained
Very many programming and scripting languages today allows you to use callback functions, both built-in functions and user defined functions. This is all good and dandy, but unfortunately very many programmers don’t know what a callback function is or does, and definately not how to use one. In this article I will try to explain and show how to use callbacks. The language used in this article is PHP because it’s easy to understand and work with, but the theory can be applied to any language supporting callback functions.
So what exactly is a callback function?
A callback-function has the exact same signature as any other function; because it is a normal function. It may take some parameters to work with, and it may return a value, but neither are required. This implies that “normal” functions may also be used as callback functions. In reality though, most callback functions will take one or more parameters, work with this and return a value.
The biggest difference between a normal function and a callback function is that the calling/parent function may not know the name or signature of the callback function at compile time, but rather at run time. In PHP, these times are normally the same, as most people run PHP as an interpreted language.
Now this can be a tough concept to grasp, hence we proceed with an example.
// In order to keep the code small and clean, there is no input checking being done.
// SETUP
// This is our function that will be used as a callback function
function sqr( $var )
{
return $var*$var;
}
$int_arr = array( 1, 2, 3 );
// END SETUP
// Call like normal function
echo 'Normal call: '.sqr( 5 );
// call within another function
function var_sqr( $i )
{
echo 'Within another function: '.sqr( $i );
}
var_sqr(5);
// Call as a callback function
function sqr_arr( $arr, $callback )
{
foreach( $arr as $i )
{
echo 'Callback-call: '.call_user_func( $callback, $i );
}
}
sqr_arr( $int_arr, 'sqr' );
The result should print something like this (with newlines added)
Normal call: 25
Within another function: 25
Callback-call: 1
Callback-call: 4
Callback-call: 9
The two first you should be able to predict on your own. The third one on the other hand, is what this article is really about.
As you can see we call the arr_sqr function like this
arr_sqr( $int_arr, 'sqr' );
The first parameter is just a normal array with three integers. No magic going on there. The second parameter on the other hand, is somewhat fishy. Here we provide the name of the function. We don’t provide any parameters or any magic to this second function, just the name.
The foreach loop in the var_sqr function shouldn’t be unfamiliar either. It just iterates over the array, and for each value in the array runs the loop body.
The call_user_func() call on the other hand, is somewhat frightning and scary.
call_user_func() is what we may call the “function executer”, it takes care of calling our callback function and providing it with parameters. call_user_func() takes an infinite number of parameters, but the first one has to be the name of function. The trailing parameters to call_user_func() are the same parameters that will be sent to the callback function, in the same order they are listed in call_user_func().
Example:
call_user_func( 'myfunc', $1, $2, $3 );
Here we are calling a function called myfunc() which takes at least three parameters. It will be called as follow:
myfunc( $1, $2, $3 )
So why use callback functions?
Callback functions are excellent for writing modular code. For instance we have this code:
/*
List the directory $dir, and apply the $filter_func function to all the elements to filter out the unwanted elements.
return the array of wanted elements from $dir
$filter_func should take one parameter (a name of a file or directory) and return true or false
*/
function listDir($dir, $filter_func) {
$ret = array();
if(function_exists($filter_func) && is_callable($filter_func)) {
if(is_dir($dir)) {
if(is_readable($dir)) {
$curr_dir = getcwd();
// undocumented notice on error?
if(@chdir($dir)) {
if($fp = opendir('.')) {
while(false !== ($file = readdir($fp))) {
if($file != '.' && $file != '..') {
if(call_user_func($filter_func, $file))
$ret[] = $file;
}
}
closedir($fp);
}
chdir($curr_dir);
}
}
}
}
return $ret;
}
This function takes two parameters, a directory/path and a callback function. As the name implies, this function will list the files in the path given by the $dir parameter. What might not be so obvious is the second parameter, $filter_func. As mentioned this argument is the name of the callback function, and it should take exactly one parameter and return true or false.
The listDir() function will then use the callback function to determine whether or not to add the file in question to the list(returned array) or not.
For instance: Say we have a function is_image() which takes one parameter, and determines if the file or directory given in the parameter is an image or not. Then we could list all images in a directory like this.
$images = listDir( 'somedir/', 'is_image' );
print_r( $images );
Or say we only wanted to list subdirectories of $dir, we could do this (note that we are using the in-built function is_dir())
$dirs = listDir( 'somedir/', 'is_dir' );
print_r( $dirs );
One could even make an advanced version of is_dir() which prints the name of the “file” if it is a directory, and called listDir() on this before it returned true and that way print all directories recursively. Note that I write “print all directories”. This is because listDir() wouldn’t save the value from is_dir() if it returned an array of directories anyway, as it expects is_dir() to return true or false. But if is_dir() PRINTS the directory it will make a recursive list. (This is left as an excercise to the reader phew)
I hope this can work as a primer for using callback functions to modularize your code. By doing this will avoid having to re-invent the wheel only to change the rims.
Hi
Ok this is over complicated. Im sure the following will work, and its easier:
function example ( $somevar, $callback ) {
}
HI, Thanks for this information.
@glen: yes this function is easier
HI, Thanks for this information.
@glen: yes this function is easier