Capturing variables with use
As you already know, a function does not see the file's variables: inside it there are only its parameters and its own variables. An anonymous function works the same way. Let's see what happens when it reaches for a variable outside:
<?php
$rate = 2;
$scale = function ($n) {
return $n * $rate; // ✗
};
echo $scale(3), "\n";Warning: Undefined variable $ratePHP warns that there is no $rate inside the function, treats it as null and prints 0. The file's variable is invisible to the anonymous function, even though it is written two lines above.
Back to the recipe card. You hand it to the pastry chef, but the grocery prices stayed in your kitchen, and the chef does not know them.
So you write the numbers you need right on the card: "milk: 80 rubles". The chef works from the card, even if the price in your kitchen changes tomorrow.
The use list
An anonymous function gets outer variables through a use list after its parameters:
<?php
$rate = 2;
$scale = function ($n) use ($rate) {
return $n * $rate;
};
echo $scale(3), "\n"; // 6
$rate = 10;
echo $scale(3), "\n"; // 6Let's go step by step:
use ($rate)lists the outer variables the function needs. Several variables are separated by commas:use ($price, $discount).- When the function is created, PHP copies the current value of
$rate, the number2, into it. That is the price written on the card. - The first call
$scale(3)takes the copy and returns6, andechoprints it. $rate = 10;changes the outer variable, while the copy inside the function stays2. That is why the second call returns6too.
In PHP an anonymous function is also called a closure, which is where the class name Closure comes from. The word fits: the function closes over the variables it captured and carries them along.
A reference instead of a copy
Sometimes a function must see the variable's latest value. For that, you put & before the name in use: use (&$rate) captures the variable itself, not a copy. The & means the same here as on a function parameter: not a copy, but the caller's variable.
Now the card no longer says "milk: 80 rubles" but "see the board for the price". The chef walks to the board every time, and if the price there has changed, they see the new one.
Let's compare both ways in one program. Click "Next step" and watch the captured row: for $byValue it holds the copy 2, and for $byRef the variable itself, &$rate.
1<?php2 3$rate = 2;4$byValue = function ($n) use ($rate) {5 return $n * $rate;6};7$byRef = function ($n) use (&$rate) {8 return $n * $rate;9};10$rate = 10;11echo $byValue(3), "\n";12echo $byRef(3), "\n";
The file creates the variable $rate with the value 2.
After $rate = 10, the copy in $byValue stayed the same, and the call returned 6. But $byRef read $rate itself during the call and returned 30.
Interviewers like to ask this: "What does a closure see if the variable changed after the closure was created?" The answer:
- with
use ($x), the old value: the copy was taken when the closure was created; - with
use (&$x), the new one: the closure is tied to the variable itself; - through
use (&$x), the closure can also change the outer variable.
A counter built on a closure
Capturing by reference lets a function remember state between calls. Here is a counter:
$count = 0;
$next = function () use (&$count) {
$count++;
return $count;
};
echo $next(), $next(), $next(), "\n"; // 123
echo $count, "\n"; // 3Let's go step by step:
use (&$count)ties the function to the outer variable$count.- Each call to
$next()increments$countitself and returns the new value. - Three calls in a row return
1,2and3, andechoprints them together:123. - The outer
$countis3after them: the function changed it, not a copy.
This is where the board picture is off. A note saying "see the board" only lets the chef read the price, but a closure with & writes on the board itself: $count++ changes the variable outside.
Common mistake
The counter was written with use ($count), without &:
<?php
$count = 0;
$next = function () use ($count) { // ✗
$count++;
return $count;
};
echo $next(), $next(), $next(), "\n";
echo $count, "\n";There is no error message, but the program prints 111 and 0. The closure keeps a copy of 0, and each call starts from it: $count++ increments the copy inside the call, and the copy is gone once the call ends.
The outer $count does not change at all. The fix is use (&$count), as in the counter above.
Key points
- An anonymous function does not see outer variables; the ones it needs are listed in
use. use ($x)copies the value when the closure is created, and the closure does not see later changes to$x.use (&$x)ties the closure to the variable itself: it sees changes and can change it.- A counter that lasts between calls relies on
use (&$count); withuse ($count)every call starts from the same copy.