Intended Audience
This article is aimed at developers who are new to PHP or programming in general. We'll be covering the basics of what closures and arrow functions are, how they work, and when you might want to use them in your code. So if you're already familiar with either of these features, this article might not be for you; but it might be a good chance to recap some PHP fundamentals.
Introduction
As a PHP web developer, it's almost inevitable that you'll come across closures and arrow functions in PHP at some point. They are both handy features that can be used to write pretty powerful code when used correctly.
But I'll admit, when I first started learning PHP (and to code, in general), I didn't have much of an idea of what closures were. I knew they were a thing, but I didn't really understand how they worked, when to use them, or what they achieved.
So in this blog post, I'm going to cover the basics of closures and arrow functions in PHP. We'll discuss what each of them does, where you might want to use them, and then we'll explore the differences between them.
Closures in PHP
Closures are anonymous functions (meaning they have no name) that can be stored in variables, passed as arguments to other functions, or returned as values from other functions.
For any of my Laravel readers, you might have already used PHP closures when adding database transactions.
Closures are defined using the function
keyword followed by a list of parameters, and a body enclosed in curly braces. Let's take a look at a very simple example. We'll create a closure that adds two numbers together and then returns it:
1$addition = function($a, $b) {2 return $a + $b;3};
In the code above, we've created a closure and stored it in a variable called $addition
. However, we haven't actually called the function yet, so nothing has happened. To call the function, we can do the following:
1$result = $addition(2, 3);2 3echo $result; // Output: 5
As we can see, the function has been called and the result has been stored in the $result
variable. We can then echo the result to the screen.
Similar to how you might want to type hint a typical function parameter to improve your code quality, you can also type hint closure parameters. We can also add return types to the closures to ensure the function returns the correct type of value. Let's add some type hints and return types to our closure:
1$addition = function(int $a, int $b): int {2 return $a + $b;3};
Now, we've added type hints to the parameters and a return type to the function. This means that if we pass anything other than an integer to the function, PHP will throw an error. Similarly, if the function doesn't return an integer, PHP will also throw an error.
Since closures aren't run until they are called, they are perfect for using as callbacks. This is great if you're using a function like array_map
or array_filter
where you can define how each item in the array should be processed. Let's take a look at an example:
1$names = ['john', 'jane', 'joe']; 2 3$greeting = 'Hello, '; 4 5$sentences = array_map( 6 callback: function (string $name) use ($greeting): string { 7 return $greeting.$name; 8 }, 9 array: $names,10);11 12// $sentences would be equal to:13// [14// 'Hello, John',15// 'Hello, Jane',16// 'Hello, Joe',17// ]
Let's take a quick look at what we're doing with the above code. We're creating an array of names (we could assume that this array was coming from a database or some other source). We then create a variable called $greeting
and assign it a string value of Hello,
. Again, we could assume that this is coming from a database or other source. We then create a new array called $sentences
and use the array_map
function to map over the $names
array.
The array_map
function then iterates through each of the names in the array we passed to it and passes the name to the closure. So the closure will be run once for each item in the array.
You may have noticed that we're using use
in the closure declaration. But what is this for? Closures can only access variables that are passed as function parameters or that are passed from the surrounding scope using the use
keyword. If we didn't use the use
keyword, the $greeting
variable would not be accessible inside the closure and an error would be thrown when trying to run the code.
Arrow Functions in PHP
Arrow functions (sometimes referred to as "short closures") are a similar feature to closures, but they provide a more concise syntax for writing anonymous functions with shorter and simpler expressions.
Rather than defining them using the function
keyword and wrapping the body in curly braces like with closures, PHP arrow functions use the fn
keyword and a single expression. It's also important to note that arrow functions must return a value. So they can't be used for functions that don't return a value, whereas closures can.
Let's take a look at how we could write our previous example closure that adds two numbers together as an arrow function:
1$addition = fn($a, $b) => $a + $b;2 3$result = $addition(2, 3);4 5echo $result; // Output: 5
We can also add type hints and return types to arrow functions in the same way as we did with closures:
1$addition = fn(int $a, int $b): int => $a + $b;2 3$result = $addition(2, 3);4 5echo $result; // Output: 5
As we can see, the arrow function syntax is shorter than the closure syntax. This can make them much easier to write, especially with shorter functions.
Only Allows Single Expressions
Arrow functions in PHP do have a downside in comparison to closures. They're limited to a single statement, whereas closures can contain multiple statements.
For example, let's imagine we had a basic closure that takes a name, uppercases the first letter of the name passed to it, and then returns the name with a greeting. But if the name passed to the closure is Ashley
, we'll return the string Hello, Ash Allen
instead. The closure could look something like this:
1$greeting = 'Hello, '; 2 3$greet = function (string $name) use ($greeting): string { 4 if ($name === 'Ashley') { 5 return 'Hello, Ash Allen'; 6 } 7 8 $upperCasedName = ucfirst($name); 9 10 return $greeting.$upperCasedName;11};12 13echo $greet('Ashley'); // Output: Hello, Ash Allen14echo $greet('john'); // Output: Hello, John
Since the closure contains two statements, we can't simply switch out the function
for fn
to convert it to an arrow function. It would require some refactoring to make it work. We could do something like this:
1$greeting = 'Hello, ';2 3$greet = fn (string $name): string =>4 $name === 'Ashley'5 ? 'Hello, Ash Allen'6 : $greeting.ucfirst($name);7 8echo $greet('Ashley'); // Output: Hello, Ash Allen9echo $greet('john'); // Output: Hello, John
As you can see in the code above, we've had to change the way the code inside the function is written so that it can be executed in a single statement. Although I personally think it looks much cleaner and easier to read how we've rewritten it, I'm hoping it's highlighted the fact that the arrow functions can sometimes be more restrictive than closures.
As the code inside your arrow functions becomes more complex, you may find that you need to consider using closures instead. Or, you may decide to extract the closure's code into another function that can be called.
After all, it's more important to write code that is easy to understand and maintain than it is to write code that is unreadable just for the sake of trying to fit it on one line.
However, if you're writing simple functions that only contain a single statement, then arrow functions are a great way to write them. For instance, I love using them to add constraints to my database queries in my Laravel apps. For example, let's say I want to write a query to grab all the authors (and some statistics about them) from my database that have published books. I could write the query using closures to write something like this:
1$authors = Author::query() 2 ->whereHas( 3 'books', 4 function (Builder $query): Builder { 5 return $query->where('is_published', true); 6 } 7 ) 8 ->withCount([ 9 'reviews' => function (Builder $query): Builder {10 return $query->where('rating', 5);11 },12 'sales' => function (Builder $query): Builder {13 return $query->where('purchased_at', '>=', now()->subDays(7));14 }15 ])16 ->get();
Alternatively, I could write the same query using arrow functions like so:
1$authors = Author::query() 2 ->whereHas( 3 'books', 4 fn (Builder $query): Builder => $query->where('is_published', true) 5 ) 6 ->withCount([ 7 'reviews' => fn (Builder $query): Builder => $query->where('rating', 5), 8 'sales' => fn (Builder $query): Builder => $query->where('purchased_at', '>=', now()->subDays(7)), 9 ])10 ->get();
Automatic Variable Capturing
One of the biggest differences between arrow functions and closures you should be aware of is that arrow functions automatically capture variables from the parent scope. This means that you don't need to use the use
keyword to capture variables like you would with a closure. You may have noticed this in the examples above where we didn't need to use the use
keyword to capture the $greeting
variable inside the arrow function.
When to Use Closures or Arrow Functions
You should always choose the most appropriate tool for the job. So there's not really a right or wrong answer when it comes to choosing between closures and arrow functions. It's more about choosing the one that is most appropriate for the task at hand.
I'd strongly encourage you to use the one that allows you to write the most readable and maintainable code. For example, if you find that you're trying to cram a lot of complex logic inside your arrow functions, then this might be a sign that you should switch to closures instead. However, if you're writing simple functions that only contain a single statement, then arrow functions may be a more suitable approach.
Here are a few pointers to help you decide which to use...
Use closures when:
- Your anonymous function requires multiple statements or complex logic.
- You need finer control over variable scoping and don't want the parent scope to be automatically captured.
Use arrow functions when:
- You want to write short, simple, and concise anonymous functions.
- The function consists of a single expression.
- You want automatic variable capturing from the parent scope.
What is the Difference Between Callbacks and Closures in PHP?
When you're reading about PHP, you'll often see the terms "callback" and "closure" being used (sometimes interchangeably). However, they're not the same thing.
As we've already discussed, a closure is an anonymous function that can be assigned to a variable and passed around like any other variable.
A callback, on the other hand, is a function that is passed to another function as an argument. It's a function that is called by another function. This means that a callback can be an instance of a closure, but it can also be an instance of a normal function.
What is the Difference Between callable
and Closure
in PHP?
The Closure
type in PHP is a class that represents an anonymous function (whether it be a traditional closure or arrow function).
Let's take these two examples here that both work and return the same result:
1function executeClosure(Closure $closure) 2{ 3 return $closure(); 4} 5 6// ✅ This will work... 7executeClosure(function () { 8 return 'Hello World'; 9});10 11// ✅ This will work...12executeClosure(fn () => 'Hello World');
However, let's look at an example that wouldn't work because we're trying to pass a normal function to the executeClosure
function:
1function executeClosure(Closure $closure) 2{ 3 return $closure(); 4} 5 6function sayHello() 7{ 8 return 'Hello World'; 9}10 11// ❌ This will not work...12executeClosure('sayHello');
However, if we change the type hint to callable
instead of Closure
, then we can pass the name of the function to the executeClosure
function:
1function executeClosure(callable $closure) 2{ 3 return $closure(); 4} 5 6function sayHello() 7{ 8 return 'Hello World'; 9}10 11// ✅ This will work...12executeClosure('sayHello');
Using First Class Callable Syntax in PHP
In PHP 8.1, the "first class callable syntax" was introduced. This allows you to convert a callable
to an anonymous function that can then be treated as a closure.
Let's take our previous example that throws an exception when we were passing a normal function to the executeClosure
function:
1function executeClosure(Closure $closure) 2{ 3 return $closure(); 4} 5 6function sayHello() 7{ 8 return 'Hello World'; 9}10 11// ❌ This will not work...12executeClosure('sayHello');
We can update this to use the first class callable syntax instead:
1function executeClosure(Closure $closure) 2{ 3 return $closure(); 4} 5 6function sayHello() 7{ 8 return 'Hello World'; 9}10 11// ✅ This will work...12executeClosure(sayHello(...));
As we can see, we've used the sayHello(...)
syntax to convert the sayHello
function to an anonymous function that can then be passed to the executeClosure
function. If were to run sayHello(...) instanceof Closure
, then we'd see that it returns true
because it's now an anonymous function.
The first class callable syntax can be a powerful tool when working with PHP's callable, so it's definitely worth checking out the documentation for more information.
Conclusion
Hopefully, this blog post should have given you a helpful introduction to closure and arrow functions in PHP. You should now know enough to be able to start trying to use them in your own projects.
If you enjoyed reading this post, I'd love to hear about it. Likewise, if you have any feedback to improve the future ones, I'd also love to hear that too.
You might also be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.
Keep on building awesome stuff! 🚀