Reputation: 4343
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 $res
ponse from the event so there's no problem with it. But it is also use
ing $req
uest from closure A.
Here, i have a doubt about the scoping of the use
'd variable, I see two possibilities :
$res->on
. So there's many closures B with their own scope inherited once from the used variable of closure A.$req
and $res
in closure A (normal behaviour...) BUT will also replace the $req
used by precedently-created closures B. And that would be problematic if a request #1 is not answered before a request #2 arrives (this is asynchronous code based on an event loop).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
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