Problem
With a basic php script I was building, I recently experienced some weird behavior. I pared it down to the bare minimum required to reproduce the bug:
<?php
$arr = array("foo",
"bar",
"baz");
foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);
foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?
?>
This outputs:
Array
(
[0] => foo
[1] => bar
[2] => baz
)
Array
(
[0] => foo
[1] => bar
[2] => bar
)
Is this a bug, or is this some unusual behavior that is expected?
Asked by regality
Solution #1
$item is still a reference to some value after the initial foreach loop, which is also used by $arr[2]. As a result, each foreach call in the second loop, which does not call by reference, changes that value with the new value, and therefore $arr[2].
So $arr[2] and $arr[1] become $arr[0], which is ‘foo’ in loop 1. $arr[2] and value become $arr[1], which is ‘bar’ in loop 2. $arr[2] and value become $arr[2], which is ‘bar’ in loop 3. (because of loop 2).
At the first call of the second foreach loop, the value ‘baz’ is truly lost.
We’ll echo the value of $item and recursively print the array $arr for each iteration of the loop.
When the first loop is completed, we get the following result:
foo
Array ( [0] => foo [1] => bar [2] => baz )
bar
Array ( [0] => foo [1] => bar [2] => baz )
baz
Array ( [0] => foo [1] => bar [2] => baz )
$item still points to the same location as $arr[2] at the end of the loop.
When the second loop is completed, we get the following result:
foo
Array ( [0] => foo [1] => bar [2] => foo )
bar
Array ( [0] => foo [1] => bar [2] => bar )
bar
Array ( [0] => foo [1] => bar [2] => bar )
Because they are both pointing to the same location, each time array inserted a new value into $item, it also updated $arr[3] with the same value. Because the value bar was just set by the previous iteration of the loop, when the loop gets to the third item of the array, it will contain the value bar.
No. This is not a bug, but rather the behavior of a referenced item. It’d be comparable to doing something like:
for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }
A foreach loop isn’t unique in that it can’t ignore objects that are mentioned. It’s simply changing the value of that variable each time, just like you would outside of a loop.
Answered by animuson
Solution #2
$item is a reference to $arr[2] and is being overwritten by the second foreach loop as animuson pointed out.
foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);
unset($item); // This will fix the issue.
foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?
Answered by Michael Leaney
Solution #3
While this isn’t technically a bug, it is in my opinion. The issue here, in my opinion, is that we anticipate $item to fall out of scope when the loop is terminated, as it would in many other programming languages. That, however, does not appear to be the case…
This code…
$arr = array('one', 'two', 'three');
foreach($arr as $item){
echo "$item\n";
}
echo $item;
Gives the output…
one
two
three
three
As others have pointed out, your second loop overwrites the referenced variable in $arr[2], but this is only because $item never went out of scope. What are your thoughts on this… bug?
Answered by jocull
Solution #4
Rasmus Lerdorf, the original developer of PHP, appears to have a simpler explanation: https://bugs.php.net/bug.php?id=71454
Answered by qdinar
Solution #5
In my opinion, PHP’s correct behavior should be a NOTICE error. A message should be issued if a referenced variable created in a foreach loop is utilized outside the loop. It’s quite simple to fall for this type of behavior, and even more difficult to recognize it once it’s happened. And no developer will read the foreach documentation page; it is of no use to them.
To avoid this problem, you should unset() the reference after your loop. When you use unset() on a reference, it simply removes the reference without causing any damage to the original data.
Answered by John
Post is based on https://stackoverflow.com/questions/8220399/php-foreach-pass-by-reference-last-element-duplicating-bug