PHPArrow Functions

Arrow Functions

Arrow functions arrived in PHP 7.4 as a compact alternative to anonymous functions for the extremely common case of "take a value, transform it, return the result." Where a closure needs function, a parameter list, a body wrapped in braces, and often a use() clause, an arrow function collapses all of that into a single expression written with fn. The trade-off is that arrow functions can only ever contain one expression — there is no room for multiple statements, loops, or conditionals with bodies.

Basic syntax

fn(params) => expression

PHP
<?php
$double = fn ($x) => $x * 2;

echo $double(21);
42

There is no return keyword and no curly braces — the value of the single expression after => is automatically returned. Multiple parameters and default values work exactly as they do in a regular function signature.

Multiple parameters and a default value

PHP
<?php
$formatPrice = fn ($amount, $currency = 'CAD') => "{$amount} {$currency}";

echo $formatPrice(19.99);
echo "\n";
echo $formatPrice(45, 'USD');
19.99 CAD
45 USD
Automatic capture of outer-scope variables

This is the headline feature that separates arrow functions from anonymous functions: an arrow function automatically captures, by value, any variable from the enclosing scope that it references. You never write a use() clause — PHP figures out which outer variables the expression touches and makes them available implicitly.

No use() clause needed

PHP
<?php
$taxRate = 0.13;

$withTax = array_map(fn ($price) => $price * (1 + $taxRate), [100, 200, 50]);

print_r($withTax);
Array
(
    [0] => 113
    [1] => 226
    [2] => 56.5
)

Compare that with the equivalent anonymous function, which would raise an "undefined variable" warning unless you added use ($taxRate) explicitly. The arrow function version needs nothing extra — $taxRate is simply visible.

Closure vs. arrow function, side by side

The two snippets below do exactly the same thing. The closure spells out every step; the arrow function reduces it to the one expression that actually matters.

Same logic, two styles

PHP
<?php
$minStock = 5;

// As a closure — needs an explicit use() clause
$isLowStock = function ($item) use ($minStock) {
    return $item['quantity'] < $minStock;
};

// As an arrow function — captures $minStock automatically
$isLowStockArrow = fn ($item) => $item['quantity'] < $minStock;

$item = ['name' => 'Widget', 'quantity' => 3];

var_dump($isLowStock($item));
var_dump($isLowStockArrow($item));
bool(true)
bool(true)
The single-expression limitation
No statement bodies allowed
An arrow function's body must be exactly one expression. You cannot put a semicolon-separated sequence of statements, an `if` block with braces, a loop, or an intermediate variable assignment inside it. Trying to do so is a parse error, not a runtime warning — PHP will refuse to even load the file.

This does NOT work

PHP
<?php
// Parse error: arrow functions cannot contain multiple statements
$process = fn ($x) => {
    $y = $x * 2;
    return $y + 1;
};

If your logic needs more than one step — a temporary variable, a loop, several conditionals, logging, error handling — you need a full closure or a named function instead. The ternary operator and short-circuiting (&&, ||, ??) can sometimes squeeze surprisingly complex logic into a single expression, but resist the temptation to write unreadable one-liners just to stay inside fn.

When to prefer fn() vs. a full closure
  • Use an arrow function for short, single-expression transforms — the classic case being a callback passed to array_map, array_filter, or usort.

  • Use a full closure when the logic needs multiple statements: intermediate variables, loops, or several conditional branches.

  • Use a full closure when you need by-reference capture (use (&$total)) — arrow functions only ever capture by value, automatically.

  • Reach for a closure or named function once an arrow function starts nesting ternaries just to avoid a statement body — readability should win.

Chaining arrow functions with array_filter and array_map

PHP
<?php
$orders = [120, 45, 300, 15, 80];

$summary = array_map(
    fn ($amount) => '$' . $amount . ' (with tax: $' . round($amount * 1.13, 2) . ')',
    array_filter($orders, fn ($amount) => $amount >= 50)
);

print_r($summary);
Array
(
    [0] => $120 (with tax: $135.6)
    [2] => $300 (with tax: $339)
    [4] => $80 (with tax: $90.4)
)
Arrow functions are still Closure instances
Even though the syntax looks different, `fn ($x) => $x * 2` produces an object of type `Closure`, exactly like the `function () {}` form. `is_callable()`, type-hinting a parameter as `callable`, and passing the value around all work identically regardless of which syntax created it.
Tip
Default to arrow functions for anything you would describe in one breath, like "square each number" or "keep only active users." The moment you catch yourself wanting a second statement, stop and switch to a full `function () use (...) { ... }` closure rather than contorting the expression to fit on one line.