Arrow Functions
What Is an Arrow Function?
In simple terms: An arrow function is a short way to write a small anonymous function whose whole job is to calculate and return one expression. Technically: Arrow functions (PHP 7.4+) use the syntax fn(parameters) => expression. The expression's value is returned automatically. Arrow functions are Closure objects, like anonymous functions, but they automatically capture any variable from the surrounding scope that they use — by value — so no use clause is needed.
// Anonymous function
$withTax = function (float $amount) use ($rate): float {
return $amount * (1 + $rate / 100);
};
// Arrow function — same behavior
$withTax = fn(float $amount): float => $amount * (1 + $rate / 100);
Why Arrow Functions Were Added
Most callbacks passed to array_map(), array_filter(), and usort() are one-liners. With anonymous functions, they need function, use, return, braces, and a semicolon. Arrow functions remove that noise, making short callbacks easier to read.
Syntax
fn(parameters): ReturnType => expression
| Part | Meaning |
|---|---|
fn | Keyword that starts an arrow function |
(parameters) | Parameters, with optional types and defaults |
: ReturnType | Optional return type |
=> | Separates parameters from the expression |
expression | A single expression; its value is returned |
Example
<?php
$double = fn(int $n): int => $n * 2;
$fullName = fn(string $first, string $last): string => "$first $last";
$isAdult = fn(int $age): bool => $age >= 18;
echo $double(21) . "\n";
echo $fullName('Priya', 'Sharma') . "\n";
var_dump($isAdult(16));
Output
42
Priya Sharma
bool(false)
There is no return keyword: the value of the expression after => is the return value.
Automatic Capture of Outer Variables
Arrow functions can use variables from the surrounding scope directly:
<?php
$gstPercent = 18;
$withGst = fn(float $amount): float => round($amount * (1 + $gstPercent / 100), 2);
echo $withGst(100) . "\n";
echo $withGst(250) . "\n";
Output
118
295
$gstPercent is captured automatically. Only variables that are actually used in the expression are captured.
Captured by Value, at Creation Time
<?php
$gstPercent = 18;
$withGst = fn(float $amount): float => $amount * (1 + $gstPercent / 100);
$gstPercent = 28; // changed after the arrow function was created
echo $withGst(100) . "\n";
Output
118
The arrow function captured 18 when it was created. Later changes to $gstPercent do not affect it.
Arrow Functions Cannot Modify Outer Variables
Because variables are captured by value, changes inside the arrow function affect only its copy:
<?php
$count = 0;
$increment = fn() => $count++;
$increment();
$increment();
echo "Count: $count\n";
Output
Count: 0
The outer $count is unchanged. There is no way to capture by reference in an arrow function — use an anonymous function with use (&$count) when shared state is needed.
Arrow Functions With Array Functions
This is where arrow functions shine:
<?php
$prices = [499, 1299, 89, 2499];
$names = ['Karthikeyan', 'Anu', 'Meena', 'Bala'];
$stock = ['Keyboard' => 12, 'Mouse' => 0, 'Monitor' => 4];
$doubled = array_map(fn(int $p): int => $p * 2, $prices);
$affordable = array_filter($prices, fn(int $p): bool => $p < 1000);
$available = array_filter($stock, fn(int $qty): bool => $qty > 0);
usort($names, fn(string $a, string $b): int => strlen($a) <=> strlen($b));
echo implode(', ', $doubled) . "\n";
echo implode(', ', $affordable) . "\n";
echo implode(', ', array_keys($available)) . "\n";
echo implode(', ', $names) . "\n";
Output
998, 2598, 178, 4998
499, 89
Keyboard, Monitor
Anu, Bala, Meena, Karthikeyan
Each callback fits on one line and reads almost like a description: "map each price to price × 2", "keep prices under 1000", "sort names by length".
Nested Arrow Functions
An arrow function can return another arrow function. Inner functions also capture variables automatically, including parameters of the outer function:
<?php
$multiplier = fn(int $x): Closure => fn(int $y): int => $x * $y;
$triple = $multiplier(3);
echo $triple(4) . "\n";
echo $multiplier(5)(6) . "\n";
Output
12
30
$multiplier(3) returns a new arrow function that remembers $x = 3. $multiplier(5)(6) creates and immediately calls such a function.
Limitations of Arrow Functions
| Limitation | Consequence | Alternative |
|---|---|---|
| Only one expression | No multiple statements, loops, or if blocks | Anonymous function with a body |
| Captures by value only | Cannot change outer variables | function () use (&$x) { ... } |
| Always returns the expression's value | Cannot be a "statement-only" function in the usual sense | Anonymous function |
Expressions can still be fairly rich — ternaries, match, function calls, and the null coalescing operator are all expressions:
<?php
$label = fn(int $stock): string => match (true) {
$stock === 0 => 'Out of stock',
$stock < 5 => "Only $stock left",
default => 'In stock',
};
foreach ([0, 3, 20] as $qty) {
echo $label($qty) . "\n";
}
Output
Out of stock
Only 3 left
In stock
If an arrow function becomes hard to read on one or two lines, switch to an anonymous or named function.
Arrow Functions vs Anonymous Functions
| Feature | Arrow Function | Anonymous Function |
|---|---|---|
| Introduced | PHP 7.4 | PHP 5.3 |
| Syntax | fn($x) => $x * 2 | function ($x) { return $x * 2; } |
| Body | Single expression | Any statements |
return | Implicit | Explicit |
| Outer variables | Captured automatically, by value | Captured explicitly with use (by value or reference) |
$this in classes | Bound automatically | Bound automatically |
static form | static fn(...) => ... | static function (...) { ... } |
| Best for | Short callbacks and small transformations | Multi-step logic, shared state |
Practical Example: Order Report
<?php
$orders = [
['id' => 1, 'customer' => 'Asha', 'total' => 1250, 'status' => 'paid'],
['id' => 2, 'customer' => 'Bala', 'total' => 640, 'status' => 'pending'],
['id' => 3, 'customer' => 'Charu', 'total' => 3200, 'status' => 'paid'],
['id' => 4, 'customer' => 'Dev', 'total' => 980, 'status' => 'paid'],
];
$minimumTotal = 1000;
$largePaidOrders = array_filter(
$orders,
fn(array $order): bool => $order['status'] === 'paid' && $order['total'] >= $minimumTotal
);
$lines = array_map(
fn(array $order): string => "#{$order['id']} {$order['customer']}: ₹" . number_format($order['total']),
$largePaidOrders
);
$sum = array_sum(array_column($largePaidOrders, 'total'));
echo implode("\n", $lines) . "\n";
echo "Total: ₹" . number_format($sum) . "\n";
Output
#1 Asha: ₹1,250
#3 Charu: ₹3,200
Total: ₹4,450
Step-by-Step Explanation
- The filter arrow function captures
$minimumTotalautomatically and keeps paid orders of at least ₹1,000. Dev's order (₹980) is excluded by the minimum, and Bala's by its status. - The map arrow function converts each remaining order into a formatted line.
array_column()extracts the totals, andarray_sum()adds them.- Each step is a small, readable expression; no temporary loops or counters are needed.
Arrow Functions and the Pipe Operator (PHP 8.5+)
The pipe operator |> passes a value through a sequence of single-argument callables. Arrow functions used in a pipe must be wrapped in parentheses:
<?php
$slug = ' PHP Arrow Functions '
|> trim(...)
|> strtolower(...)
|> (fn(string $s): string => str_replace(' ', '-', $s));
echo $slug;
Output
php-arrow-functions
This requires PHP 8.5. On earlier versions, use nested calls or intermediate variables.
Common Mistakes
Trying to Use Multiple Statements
$fn = fn($x) => { $y = $x * 2; return $y; }; // parse error
Fix: Use an anonymous function for multi-statement logic.
Expecting Changes to Outer Variables
Effect: Outer variables stay unchanged because they were captured by value. Fix: Return the new value, or use an anonymous function with use (&$var).
Writing return in the Expression
$fn = fn($x) => return $x * 2; // parse error
Fix: The expression itself is returned: fn($x) => $x * 2.
Overly Long Arrow Functions
Effect: Dense one-liners that are hard to read and debug. Fix: Extract the logic into a named function and pass it with first-class callable syntax.
Best Practices
- Use arrow functions for short, single-expression callbacks.
- Add parameter and return types even in short callbacks.
- Rely on automatic capture for read-only values; use anonymous functions for shared state.
- Prefer
static fninside classes when$thisis not needed. - Switch to a named function when the expression stops being obvious at a glance. Official reference: https://www.php.net/manual/en/functions.arrow.php