It’s time for callback functions. What is a callback function? It’s just a function that’s passed as an argument to another function and is then executed inside the other function. I think it’s time to go through this concept in excruciating detail to put this behind us once and for all. We’ll cover passing anonymous functions (closures) and arrow functions as arguments to other functions, and then calling them too.
Callback functions make a lot of sense in asynchronous languages since we may not want two functions to compete for finishing times. One function might be dependent on the result of another function, so we would need the two functions to execute synchronously (one after the other).
We’re used to passing integers, strings, and even arrays as arguments into functions, but we might not be familiar with passing functions themselves as arguments into other functions.
If you’re not familiar with the concept of how functions work, I suggest that you read my other articles on the concepts. Here are a few that you might want to familiarize yourself with before proceeding.
PHP — P35: User Defined Functions
PHP — P36: Function Arguments
PHP — P37: Functions Returning Values
PHP — P38: Variable Functions
PHP — P39: Anonymous Functions
PHP — P40: use Keyword
PHP — P41: Arrow Functions
I will recap a few concepts briefly just so that the overall picture makes more sense. We’ll create a function add_one_to_x() that accepts one argument. The function will then add 1 to the argument and echo out the result.
<?php
function add_one_to_x( $x ) {
echo $x + 1;
}
add_one_to_x( 5 );
?>
Pretty standard so far. We passed an integer as an argument. The way that you can think about it is that the value 5 was assigned to the variable (parameter) $x. Just imagine that we moved $x into the function body. The result would be the same.
<?php
function add_one_to_x() {
$x = 5;
echo $x + 1;
}
add_one_to_x();
?>
Of course this doesn’t give us the flexibility to pass an argument to the function since it’s hard-coded inside of the function body, but it’s important to have this visual. We can also visualize this by assigning a default value to the parameter.
<?php
function add_one_to_x( $x = 5 ) {
echo $x + 1;
}
add_one_to_x();
?>
The whole purpose of these basic examples is to have a visual of the movement of the argument from the function call to the function definition. Let’s see what happens when we pass the argument to the add_one_to_x() function.
<?php
function add_one_to_x( $x ) {
echo $x + 1;
}
add_one_to_x( 5 );
?>
I am sure that you already knew this. I think we spent enough time on this concept so let’s move on to the next concept.
Did you know that there is another way to call functions in PHP?
<?php
function say_hello() {
echo "Hello";
}
say_hello();
call_user_func( "say_hello" );
?>
You’ve probably called functions like I did on line 7 above. But, PHP has a built in function, call_user_func(), that accepts a string as an argument. That string is the name of the function that you want to call. The call_user_func(“say_hello”) statement on line 8 does the exact same thing as say_hello() on line 7. There’s no trickery here: it literally is just another way to call a function.
Let’s create a function called animal_says(). Initially, that function will accept two arguments. Those arguments will just be echoed out once the function is called.
<?php
function animal_says( $animal_type, $animal_saying ) {
echo $animal_type . " says ";
echo $animal_saying;
}
animal_says( "Cat", "Dogs suck" );
?>
In the example above, the two arguments are passed to the animal_says() function. That function will then echo out Cat says Dogs suck. I created two echo statements since we’ll be modifying this function next.
Let’s change the second echo statement. Instead of directly echoing out the argument $animal_saying, we’ll call another function that will do that for us.
<?php
function animal_says( $animal_type, $animal_saying ) {
echo $animal_type . " says ";
cat_says( $animal_saying );
}
function cat_says( $saying ) {
echo $saying;
}
animal_says( "Cat", "Dogs suck" );
?>
We start by creating a new function, cat_says(). That function accepts one argument and it echoes out the value when it’s called. The cat_says() function is called from within the animal_says() function. Those were the changes. Let’s walk through this example and see what’s happening. We still haven’t gotten to the concept of callback functions just yet, but we’re really close.
- PHP reaches line 12. The function animal_says() is called and two arguments are passed: the strings Cat and Dogs suck.
- PHP enters the function animal_says() on line 3. The first argument is concatenated to the string “ says “ and is echoed out.
- The second argument, Dogs suck, is passed as an argument to the cat_says() function.
- PHP enters the cat_says() function. The argument Dogs suck is echoed out.
- PHP exits the cat_says() function and returns to the animal_says() function.
- PHP exits the animal_says() function and finishes the execution of the program after line 12.
- The total output is: Cat says Dogs suck.
If I know cats, and I do, they will always say that Dogs suck. So what’s the point in passing the argument in the first place to the cat_says() function. We can just echo it right there.
<?php
function animal_says( $animal_type ) {
echo $animal_type . " says ";
cat_says();
}
function cat_says() {
echo "Dogs suck";
}
animal_says( "Cat" );
?>
The dogs were offended. They want to have their own function as well. Let’s create a dog_says() function. They want to always say that Cats suck.
<?php
function animal_says( $animal_type ) {
echo $animal_type . " says ";
cat_says();
}
function cat_says() {
echo "Dogs suck";
}
function dog_says() {
echo "Cats suck";
}
animal_says( "Cat" );
animal_says( "Dog" );
?>
Let’s imagine that we don’t have access to the cat_says() and dog_says() functions directly and that we only have access to the animal_says() function (you’ll see this concept in object oriented programming, which we’ll cover later articles).
If we call the animal_says() function and pass it the Cat argument, we will get the following output: Cat says Dogs suck. If we call the animal_says() function and pass it the Dog argument, we will get the following output: Dog says Dogs suck. That’s not what we want however; we want it to say Dog says Cats suck. Currently, the cat_says() function is hard-coded on line 5. We could do an if/else statement and call the cat_says() function if the Cat argument is passed, and call the dog_says() function if the Dog argument is passed.
<?php
function animal_says( $animal_type ) {
echo $animal_type . " says ";
if ( $animal_type == "Cat" ) {
cat_says();
} elseif ( $animal_type == "Dog" ) {
dog_says();
}
}
function cat_says() {
echo "Dogs suck";
}
function dog_says() {
echo "Cats suck";
}
animal_says( "Cat" );
animal_says( "Dog" );
?>
This does work, but what if we wanted to add a function for every animal? Each type of animal wants to say something unique, and inconsistent, so we’ll have to create functions for each animal. That means that we’ll have a massive if/else statement.
Let’s eliminate that headache now. Wouldn’t it be nice to just pass which function we want and have it called automatically without the if/else statement? Remember the call_user_func() function? If we pass the name of the function to it, we can call the specific function.
Our animal_says() function will need to accept a second argument. That second argument will be the name of the function that we want to call. We’ll then pass that string argument to the call_user_func() function and have PHP call the function.
<?php
function animal_says( $animal_type, $animal_function ) {
echo $animal_type . " says ";
call_user_func( $animal_function );
}
function cat_says() {
echo "Dogs suck";
}
function dog_says() {
echo "Cats suck";
}
animal_says( "Cat", "cat_says" );
animal_says( "Dog", "dog_says" );
?>
Let’s walk through the example and make sure that we get the results that we want.
- The animal_says() function is called on line 16 and is passed two arguments: the string Cat and the string cat_says.
- PHP enters the function animal_says on line 3. It enters the body of the function and executes the echo statement on line 4: Cat says.
- It then moves to the next line (5) and passes the $animal_function argument to the call_user_func() function. The string “cat_says” is passed to the call_user_func(). The call_user_func() function itself calls the cat_says() function, which in turn echoes out Dogs suck.
- PHP exits the cat_says() function and returns to the animal_says() function.
- PHP exits the animal_says() function and returns to line 16. The statement on line 16 is over and PHP moves to line 17.
- PHP calls the animal_says() function and passes two arguments to it: the string Dog and the string dog_says.
- PHP enters the function animal_says on line 3. It enters the body of the function and executes the echo statement on line 4: Dog says.
- It then moves to the next line (5) and passes the string dog_says to the call_user_func(). The call_user_func() function itself calls the dog_says() function, which in turn echoes out Cats suck.
- PHP exits the dog_says() function and returns to the animal_says() function.
- PHP exits the animal_says() function and returns to line 17. The statement on line 17 is over and PHP finishes the program.
That’s almost what a callback function is. The function name, which is a string, is passed as an argument, and is executed inside of the animal_says() function. And to be honest, you could stop here and execute functions this way. The only thing that you would need to know is how to pass arguments to the functions that are called with the call_user_func() function. Let’s just make sure that we have all of our bases covered and discuss that concept too.
Our cats and dogs sat down and worked out some differences. It turns out that they want to be able to say different things and not just Dogs suck or Cats suck. The cat_says() and dog_says() functions will have to be modified so that they can accept an argument and echo that argument out. The call_user_func() has a second optional parameter, which allows the user to pass an argument to the function.
call_user_func( $function_name, $function_arg = "" )
Writing out call_user_func(“dog_says”, “Hello”) would be the same as calling the dog_says() function like this: dog_says(“Hello”). Let’s put those principles into practice.
<?php
function animal_says( $animal_type, $animal_function, $saying ) {
echo $animal_type . " says ";
call_user_func( $animal_function, $saying );
}
function cat_says( $cat_saying ) {
echo "Meow: " . $cat_saying;
}
function dog_says( $dog_saying ) {
echo "Woof: " . $dog_saying;
}
animal_says( "Cat", "cat_says", "I love dogs" );
animal_says( "Dog", "dog_says", "Cats still suck" );
?>
The animal_says() function now accepts three arguments:
- the string $animal_type that will be echoed out on line 4.
- the string $animal_function that will be passed to the call_user_func() function so that it can execute a function call.
- the string $saying that will also be passed to the call_user_func() function. This time, it will be passed as an argument to the function that the call_user_func() function is told to execute.
So, looking at line 16, the animal_says() function is called and is passed the three arguments: strings Cat, cat_says, and I love dogs. Cat is echoed out on line 4 and the two other arguments are passed to the call_user_func() function on line 5. The call_user_func() executes the function call cat_say($saying) and passes the I love dogs argument to it. PHP executes the echo statement on line 9, Meow: I love dogs. The process is repeated on line 17 with new arguments that trigger the dog_says() function to be called.
We don’t have to use the call_user_func() function for our function call. Remember variable functions? Variable functions can be beneficial when you want to call a function dynamically based on the value stored inside of the variable. As a quick recap, you can store the name of a function as a string inside of a variable, and call the function by appending a couple of parentheses to the variable.
<?php
function subaru() {
echo "You're getting an STi";
}
function nissan() {
echo "You're getting a Skyline";
}
$car = "subaru";
$car();
$car = "nissan";
$car();
?>
We can use this concept to replace our call_user_func() since the name of the function is stored inside of our $animal_function parameter. We’ll just replace call_user_func($animal_function, $saying) with $animal_function($saying); we’re just modifying line 5.
<?php
function animal_says( $animal_type, $animal_function, $saying ) {
echo $animal_type . " says ";
$animal_function( $saying );
}
function cat_says( $cat_saying ) {
echo "Meow: " . $cat_saying;
}
function dog_says( $dog_saying ) {
echo "Woof: " . $dog_saying;
}
animal_says( "Cat", "cat_says", "I love dogs" );
animal_says( "Dog", "dog_says", "Cats still suck" );
?>
I know that I’m incrementally making changes, and I’m doing this cautiously. I want to make sure that you understand each progression.
Next, the only change that we’re going to do is change the parameter name from $animal_function to $callback. It’s customary to call this parameter $callback to make it explicitly clear that the function will be called inside of this function.
<?php
function animal_says( $animal_type, $callback, $saying ) {
echo $animal_type . " says ";
$callback( $saying );
}
function cat_says( $cat_saying ) {
echo "Meow: " . $cat_saying;
}
function dog_says( $dog_saying ) {
echo "Woof: " . $dog_saying;
}
animal_says( "Cat", "cat_says", "I love dogs" );
animal_says( "Dog", "dog_says", "Cats still suck" );
?>
Are we here? Is it possible that we’re finally going to tackle callback functions? Yes. So far, we’ve been passing the name (string) of the function that we want to call inside of the animal_says() function. But remember, a callback function is a function that’s passed as an argument, not a string that’s passed as an argument.
Let’s change the functions cat_says() and dog_says() to anonymous functions. We’ll assign them to variables $cat_says and $dog_says. When we change the those, then we can pass the variable $cat_says and the variable $dog_says as arguments, instead of the strings cat_says and dog_says. Before we do that, let’s briefly recap anonymous functions and how to call them.
<?php
$cat_says = function( $cat_saying ) {
echo "Meow: " . $cat_saying;
};
$cat_says( "I love dogs" );
?>
In the example above, we create a variable $cat_says and assign the anonymous function (closure) to it. That closure has one parameter, $cat_saying, and echoes out Meow: followed by the parameter value (the value of $cat_saying). The closure is called on line 7 by appending the parentheses to the $cat_says() function and passing the argument I love dogs. Once the function is executed, the output will be Meow: I love dogs.
We have one function tackled; now, let’s just modify the second function, dog_says(), and pass the two variables to the animal_says() function. We’re not going to need to modify anything internally since variable functions and anonymous functions technically have the same syntax.
<?php
function animal_says( $animal_type, $callback, $saying ) {
echo $animal_type . " says ";
$callback( $saying );
}
$cat_says = function( $cat_saying ) {
echo "Meow: " . $cat_saying;
};
$dog_says = function( $dog_saying ) {
echo "Woof: " . $dog_saying;
};
animal_says( "Cat", $cat_says, "I love dogs" );
animal_says( "Dog", $dog_says, "Cats still suck" );
?>
Looking at the code, we changed the cat_says() function to a closure on line 8. We also changed the dog_says() function to a closure on line 12. We’re then passing the $cat_says closure and $dog_says closure as arguments on line 16 and 17. This is what developers can’t seem to wrap their heads around, so I’m going to draw it out. Let’s look at line 16 and just focus on the second argument. If you need a refresher on passing strings as arguments, you can look at the first illustration at the beginning of this article.
Since $cat_says is equal to the closure, we can just replace the argument as the closure. So just pretend that we shoved the entire function into the argument.
As you can see, the closure is passed as an argument to the animal_says() function. The closure is assigned to the $callback parameter. We now know what the $callback variable holds; it just holds the closure. How do we call a closure? By appending parentheses to the variable name and passing any arguments to it. Let’s now expand this and see how the third argument travels.
- The argument is passed when the animal_says() function is called.
- It’s assigned as the third parameter value.
- It’s passed to the $callback() function call.
- It’s passed to the closure.
- It’s echoed out from inside of the closure.
What would prevent us from just passing the entire closure as an argument instead of passing the variable that’s assigned to the closure? Well, nothing. We’re passing strings as arguments, why can’t we just pass functions as arguments? We could have assigned the string “I love dogs” to a variable and then passed the variable, but we didn’t. We just passed the string.
Let’s look at passing anonymous functions as arguments. This time, we won’t be creating the $dog_says and $cat_says closures. We’re just going to pass those closures as arguments directly.
<?php
function animal_says( $animal_type, $callback, $saying ) {
echo $animal_type . " says ";
$callback( $saying );
}
animal_says( "Cat", function( $cat_saying ) { echo "Meow: " . $cat_saying; }, "I love dogs" );
animal_says( "Dog", function( $dog_saying ) { echo "Woof: " . $dog_saying; }, "Cats still suck" );
?>
We just got rid of the two closures and passed them directly as arguments.
- On line 8, a call to animal_says() function is made.
- The first argument is the string Cat. That string is assigned to the $animal_type parameter.
- The second argument is the function: function( $cat_saying ) { … };. That function is assigned to the parameter $callback.
- The third argument is the string I love dogs. That string is assigned to the parameter $saying.
- Going inside of the animal_says() function, the first argument is echoed out on line 4.
- On line 5, we’re just calling the closure that was assigned to the $callback parameter by appending a couple of parentheses to it and passing the $saying parameter value to it.
As long as you can wrap your head around it visually and see how the closure is moving around the code, callback functions should be a breeze. I placed the anonymous function on the same line as the other two arguments, but for readability, the closure argument is usually spaced out onto multiple lines. This is just preference.
<?php
function animal_says( $animal_type, $callback, $saying ) {
echo $animal_type . " says ";
$callback( $saying );
}
animal_says(
"Cat",
function( $cat_saying ) {
echo "Meow: " . $cat_saying;
},
"I love dogs"
);
animal_says(
"Dog",
function( $dog_saying ) {
echo "Woof: " . $dog_saying;
},
"Cats still suck"
);
?>
We can also pass arrow functions instead of anonymous functions for even more simplicity. Remember that arrow functions can only return an argument. We’ll modify the code one last time.
<?php
function animal_says( $animal_type, $callback, $saying ) {
echo $animal_type . " says ";
echo $callback( $saying );
}
animal_says( "Cat", fn( $cat_saying ) => "Meow: " . $cat_saying, "I love dogs" );
animal_says( "Dog", fn( $dog_saying ) => "Woof: " . $dog_saying, "Cats still suck" );
?>
Looking at line 8, the arrow function is passed as the second argument. The arrow function will accept an argument once it’s called on line 5. That argument is I love dogs. It will return Meow: I love dogs. The entire string will be echoed out on line 5.
And that’s it. I think we covered everything there is to know about callback functions. I really hope that this makes sense now and that you can put your fears of callback functions behind you once and for all.