Morgan Touverey Quilling
Morgan Touverey Quilling

Reputation: 4343

PHP closure in other closure : scope of "use"

I have a code that looks like this :

$app->add(function($req, $res, $next) {
    # closure A
    $res->on('end', function($res) use ($req) {
        # closure B
    });
    $next();
});

As you can see, I have a closure in a closure. Closure B is receiving $response from the event so there's no problem with it. But it is also useing $request from closure A. Here, i have a doubt about the scoping of the use'd variable, I see two possibilities :

I hope I'm clear enough. I ask this because, for example, in JavaScript, we sometimes have to use callback generators to ensure that the scope of a sub-closure is not replaced.


Edit: I tried with a code that theoritically does the same thing, but which is easier to test:

$a = function($var) {
    return function() use ($var) {
        var_dump($var);
    };
};

$fn1 = $a((object) ['val' => 1]);
$fn2 = $a((object) ['val' => 2]);
$fn2();
$fn1();

The output (2, 1) showed that the first function $fn1 has kept its original scope. Also, I noticed that the output of var_dump on a Closure object shows the scope it brings:

object(Closure)#3 (1) {
  ["static"]=>
  array(1) {
    ["res"]=>
    object(stdClass)#2 (1) {
      ["val"]=>
      int(1)
    }
  }
}

Technical explanation? I think that's because closures in PHP are regular PHP objects where use is a sort of constructor.

Am I right? Any PHP specialist?

Upvotes: 5

Views: 1393

Answers (1)

Trowski
Trowski

Reputation: 469

Internally, each Closure object in PHP contains a hash table. This table stores the values that are copied into the closure's scope with the use keyword. Arrays in PHP are also implemented using a hash table.

When you use a set of variables in a closure, it is as though you've created an array containing each variable being used. Each closure contains it's own unique "array" of values that is initialized when it is created. Unlike a normal array, the table of variables used in a closure cannot be modified.

$var1 = 1;
$var2 = 2;

$closure = function () use ($var1, $var2) {
    return $var1 . ", " . $var2 . "\n";
};

$array = [$var1, $var2];

$var1 = 3;
$var2 = 4;

echo $closure(); // echoes 1, 2
echo $array[0] . ", " . $array[1] . "\n"; // echoes 1, 2

Changing the value of $var1 will not affect the value at $array[0], nor would it change the value of $var1 within $closure.

When you use an object in a closure, that object may be changed outside of the closure and those changes will be reflected in the closure. Objects are not cloned when used in a closure. However, because you cannot modify the variable itself, you cannot change the variable to point at a different object.

Variables may also be used in a closure by reference. This allows a variable value to be modified outside of a closure and those changes to be reflected within the closure itself.

$var1 = 1;
$var2 = 2;

$closure = function () use (&$var1, $var2) {
    return $var1 . ", " . $var2 . "\n";
};

$array = [&$var1, $var2];

$var1 = 3;
$var2 = 4;

echo $closure(); // echoes 3, 2
echo $array[0] . ", " . $array[1] . "\n"; // echoes 3, 2

When the closure above is created, a reference to $var1 is created in the closure's table of values, but only the value of $var2 is copied into the table. When the values of $var1 and $var2 were changed, only the value of $var1 was changed within the closure because only that variable was used by reference. This again is similar to creating an array where $var1 is added to the array by reference, but the value of $var2 is copied to the array.

When a closure is created within a closure, the internal closure is copying the value of the variables at the time the closure is created. It does not matter that it is being created within another closure.

$value = 1;

$closure = function ($arg) use ($value) {
    return function () use ($arg, $value) {
        return $value + $arg;
    };
};

$value = 10;

$callback1 = $closure(1);
$callback2 = $closure(2);

echo $callback1() . "\n"; // Echoes 2
echo $callback2() . "\n"; // Echoes 3

TL;DR: The value of a variable is copied into a closure when the closure is created. To be able to modify the value outside of the closure, the value must be used by reference (e.g., function () use (&$value) { ... }).

Upvotes: 5

Related Questions