Skip to content
Theory · Functions as values · lesson 3 of 8

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
<?php

$rate = 2;
$scale = function ($n) {
    return $n * $rate; // ✗
};
echo $scale(3), "\n";
TEXT
Warning: Undefined variable $rate

PHP 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
<?php

$rate = 2;
$scale = function ($n) use ($rate) {
    return $n * $rate;
};
echo $scale(3), "\n"; // 6
$rate = 10;
echo $scale(3), "\n"; // 6

Let's go step by step:

  1. use ($rate) lists the outer variables the function needs. Several variables are separated by commas: use ($price, $discount).
  2. When the function is created, PHP copies the current value of $rate, the number 2, into it. That is the price written on the card.
  3. The first call $scale(3) takes the copy and returns 6, and echo prints it.
  4. $rate = 10; changes the outer variable, while the copy inside the function stays 2. That is why the second call returns 6 too.

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.

Liveuse by value and by referencestep 1 of 8 · frames: 1

The file creates the variable $rate with the value 2.

Watch the captured row and $rate in the {main} frame

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:

PHP
$count = 0;
$next = function () use (&$count) {
    $count++;
    return $count;
};
echo $next(), $next(), $next(), "\n"; // 123
echo $count, "\n"; // 3

Let's go step by step:

  1. use (&$count) ties the function to the outer variable $count.
  2. Each call to $next() increments $count itself and returns the new value.
  3. Three calls in a row return 1, 2 and 3, and echo prints them together: 123.
  4. The outer $count is 3 after 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
<?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); with use ($count) every call starts from the same copy.