Arrow functions and strlen(...)
So far, a closure has been a recipe card with prices written on it through use. But some recipes are a single line, like "multiply by the rate". Nobody makes a whole card for those: a sticky note will do.
A sticky note is small: it holds one line. In return, it copies the prices mentioned in that line on its own, so you do not have to list them. In PHP, such a sticky note is an arrow function, fn.
Let's compare an anonymous function and an arrow function that do the same thing:
<?php
$rate = 2;
$long = function ($n) use ($rate) {
return $n * $rate;
};
$short = fn($n) => $n * $rate;
echo $long(3), ' ', $short(3), "\n"; // 6 6Let's go step by step:
$longis an anonymous function with auselist, curly braces andreturn.$shortdoes the same in one line. It has nouselist, yet it still sees$rate.- Both calls return
6, andechoprints6 6.
The fn syntax
An arrow function is a short anonymous function whose body is a single expression. Let's take fn($n) => $n * $rate apart:
fnis the keyword; in the PER Coding Style, the parentheses with the parameters come right after it, with no space;($n)are the parameters, as in any function; you can add types too:fn(int $n): int => $n * 2;=>separates the parameters from the body;$n * $rateis the body, one expression; the function returns its value by itself, so you do not writereturn.
An arrow function is a Closure object too. You put it in a variable, pass it to a callable parameter and call it with parentheses.
Capture without use
An arrow function captures the outer variables its expression mentions on its own. It captures them by value, at the moment it is created, as use ($rate) does:
$rate = 10;
echo $short(3), "\n"; // 6
$count = 0;
$add = fn() => ++$count;
echo $add(), ' ', $add(), ' ', $count, "\n"; // 1 1 0Let's see what happens here:
$rate = 10;changes the outer variable, but$shortcaptured the copy2and still returns6.$addincrements$count, but only its own copy. Each call starts from0and returns1.- The outer
$countstays0.
This is where the sticky-note picture is off. The note copies prices on its own, but "see the board" does not fit on it: fn cannot capture a variable by reference. A counter needs an anonymous function with use (&$count).
How an arrow function differs from an anonymous one is a frequent interview question. The answer:
fncaptures outer variables on its own, whilefunctionsees only those listed inuse;fncaptures by value only, whilefunctioncan also douse (&$x);- the body of
fnis one expression withoutreturn, whilefunctioncan hold as many statements as you like.
A closure from an existing function
Sometimes the function you need already exists, such as the built-in strlen. Since PHP 8.1, you can make a closure from it with strlen(...). The three dots in the parentheses stand where the arguments would be and mean "do not call it, make a closure".
$len = strlen(...);
$up = strtoupper(...);
echo $len('tea'), ' ', $up('tea'), "\n"; // 3 TEA
echo gettype($len), "\n"; // objectLet's go step by step:
strlen(...)does not callstrlenbut returns aClosureobject, which goes into$len.$len('tea')callsstrlen('tea')and returns3, and$up('tea')returnsTEA.echo gettype($len)printsobject, as for any anonymous function.
The string 'strlen' is a callable too, but strlen(...) has an advantage. If you misspell the name, PHP stops the program right away, on the line with (...). A misspelled string is checked only when it is called or passed to a callable parameter.
Common mistake
Someone tries to give an arrow function a body in curly braces:
<?php
$double = fn($n) => { // ✗
return $n * 2;
};
echo $double(7), "\n";Parse error: syntax error, unexpected token "{"After =>, PHP expects one expression, and statements in curly braces are never an expression. Because of the parse error, the file does not start at all. The fix is fn($n) => $n * 2, and for a body of several statements, an anonymous function with function.
Key points
fn($n) => expressionis an arrow function: one expression, whose value it returns withoutreturn.fncaptures the outer variables of its expression on its own, by value, when it is created.fncannot capture a variable by reference: that needsfunctionwithuse (&$x).strlen(...)(PHP 8.1) makes a closure from an existing function without calling it.