Reputation: 4808
Why is an empty foreach loop can change the result.
I have the following code:
$variable = [1,2,3,4];
foreach ($variable as $key => &$value)
$value ++;
var_dump($variable);
The result I get is:
array (size=4)
0 => int 2
1 => int 3
2 => int 4
3 => &int 5
Now, when I add an empty foreach loop like this:
$variable = [1,2,3,4];
foreach ($variable as $key => &$value)
$value ++;
foreach ($variable as $key => $value);
var_dump($variable);
I get this :
array (size=4)
0 => int 2
1 => int 3
2 => int 4
3 => &int 4
can someone explain me why the last element doesn't change when I add the second empty loop, and why there is a & infront of the last element?
Upvotes: 29
Views: 21591
Reputation: 2101
At the end of the first loop, $value
is pointing to the same place as $variable[3]
(they are pointing to the same location in memory):
$variable = [1,2,3,4];
foreach ($variable as $key => &$value)
$value ++;
Even as this loop is finished, $value
is still a reference that's pointing to the same location in memory as $variable[3]
, so each time you store a value in $value
, this also overwrites the value stored for $variable[3]
:
foreach ($variable as $key => $value);
var_dump($variable);
With each evaluation of this foreach, both $value
and $variable[3]
are becoming equal to the value of the iterable item in $variable.
So in the 3rd iteration of the second loop, $value
and $variable[3]
become equal to 4 by reference, then during the 4th and final iteration of the second loop, nothing changes because you're passing the value of $variable[3]
(which is still &$value
) to $value
(which is still &$value
).
It's very confusing, but it's not even slightly idiosyncratic; it's the code executing exactly as it should.
More info here: PHP: Passing by Reference
To prevent this behavior it is sufficient to add an unset($value);
statement after each loop where it is used. An alternative to the unset
may be to enclose the foreach
loop in a self calling closure, in order to force $value
to be local, but the amount of additional characters needed to do that is bigger than just unsetting it:
(function($variable){
foreach ($variable as $key => &$value) $value++;
})($variable);
Upvotes: 20
Reputation: 2240
This is a name collision: the name $value introduced in the first loop exists after it and is used in the second loop. So all assignments to it are in fact assignments to the original array. What you did is easier observed in this code:
$variable = [1,2,3,4];
foreach ($variable as $key => &$value)
$value ++;
$value = 123; // <= here you alter the array!
var_dump($variable);
and you will see $variable[3]
as 123
.
One way to avoid this is, as others said, to unset ($value)
after the loop, which should be a good practice as recommended by the manual. Another way is to use another variable in the second loop:
$variable = [1,2,3,4];
foreach ($variable as $key => &$value)
$value ++;
foreach ($variable as $key => $val);
var_dump($variable);
which does not alter your array.
Upvotes: 14
Reputation: 173582
Obligatory statement: References are evil!
Stepping through your code:
$variable = [1,2,3,4];
foreach ($variable as $key => &$value)
$value++;
After the loop completes; $value
is a reference to $variable[3]
and thus has the value of int(4)
.
foreach ($variable as $key => $value);
At each iteration, $variable[3]
gets assigned an element of $variable[<k>]
where 0 <= k < 3
. At the last iteration it gets assigned to its own value which is that of the previous iteration, so it's int(4)
.
Unsetting $value
in between the two loops resolves the situation. See also an earlier answer by me.
Upvotes: 3
Reputation: 111859
After loop you should unset this reference using:
unset($value);
So your whole code should work like this:
$variable = [1,2,3,4];
foreach ($variable as $key => &$value) {
$value++;
}
unset($value);
var_dump($variable);
There is no point to put unset($value);
inside the loop
Explanation - after loop $value is still set to the last element of array so you can use after your loop $value = 10;
(before unset) and you will see that last element of array has been changed to 10
. It seems that var_dump
want to help us a bit in this case and shows there is reference for last element and of course when we use unset
we have desired output of var_dump
.
You could also look at the following script:
<?php
$array = [1, 2, 3, 4];
var_dump($array);
$x = &$array[2];
var_dump($array);
$x += 20;
unset($x);
var_dump($array);
?>
We don't use loop here and if reference is set to element of array, var_dump
shows us this putting &
before type of this element.
However if the above code we changed reference and set it this way $x = &$array;
var_dump wouldn't show us any reference.
Also in the following code:
<?php
$x = 23;
$ref = &$x;
var_dump($x);
?>
var_dump()
won't give us any hint.
Upvotes: 4
Reputation: 6252
The last element of the array will remian even after the foreach loop ..So its needed to use unset
function outside the loop ..That is
$variable = [1,2,3,4];
foreach ($variable as $key => &$value) {
$value++;
}
unset($value);
var_dump($variable);
The link to the manual can be found here http://php.net/manual/en/control-structures.foreach.php
Upvotes: 5
Reputation: 13128
As phil
stated in the comments:
As mentioned in the manual, you should unset() references after use.
$variable = [1,2,3,4];
foreach ($variable as $key => &$value) {
$value ++;
}
unset($value);
foreach ($variable as $key => $value);
print_r($variable);
Will return:
Array
(
[0] => 2
[1] => 3
[2] => 4
[3] => 5
)
Explanation
Taken from the foreach()
manual. (See the big red box)
Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().
It basically means: That the referenced value &$value
and the last element/item in the array, which in this case is 4
remain the same. To counter-act this issue, you'll have to unset()
the value after use, otherwise it will stay in the array as its original value (if that makes sense).
You should also read this: How does PHP 'foreach' actually work?
Upvotes: 4