Reputation: 3559
<?php
$a = array('a', 'b', 'c', 'd');
foreach ($a as &$v) { }
foreach ($a as $v) { }
print_r($a);
?>
I think it's a normal program but this is the output I am getting:
Array
(
[0] => a
[1] => b
[2] => c
[3] => c
)
Can someone please explain this to me?
Upvotes: 56
Views: 6559
Reputation: 533
It is not necessary that the reference variable is the loop variable. For example (this may look contrived, but of course is an oversimplification of a program of mine),
$list = [ 'one', 'two', 'three', 'four' ] ;
$results = [] ;
foreach ($list as $item) {
$newitem = $item ;
$results[] = &$newitem ;
# unset($newitem) ;
}
var_dump($results) ;
prints
array(4) {
[0]=>
&string(4) "four"
[1]=>
&string(4) "four"
[2]=>
&string(4) "four"
[3]=>
&string(4) "four"
}
since every element of $results
points to $newitem
, which is the same variable at each iteration, and at each iteration is changed to point at the "current" element of list – so at the end it points to the last.
The commented unset()
fixes the program, ensuring we have a "fresh" $newitem
at each iteration, and the summary is, beware the scope of references!
Upvotes: -1
Reputation: 31
The first
foreach loop does not make any change to the array, just as we would expect.
However, it does cause $v
to be assigned a reference to each of $a
’s elements,
so that, by the time the first loop is over, $v
is, in fact, a reference to $a[2]
.
As soon as the second loop starts, $v
is now assigned the value of each
element. However, $v
is already a reference to $a[2];
therefore, any value
assigned to it will be copied automatically into the last element of the array!
Thus, during the first iteration, $a[2]
will become zero, then one, and then
one again, being effectively copied on to itself. To solve this problem, you
should always unset the variables you use in your by-reference foreach
loops—or, better yet, avoid using the former altogether.
Upvotes: 3
Reputation: 212412
This is well-documented PHP behaviour See the warning on the foreach page of php.net
Warning
Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().
$a = array('a', 'b', 'c', 'd');
foreach ($a as &$v) { }
unset($v);
foreach ($a as $v) { }
print_r($a);
EDIT
Attempt at a step-by-step guide to what is actually happening here
$a = array('a', 'b', 'c', 'd');
foreach ($a as &$v) { } // 1st iteration $v is a reference to $a[0] ('a')
foreach ($a as &$v) { } // 2nd iteration $v is a reference to $a[1] ('b')
foreach ($a as &$v) { } // 3rd iteration $v is a reference to $a[2] ('c')
foreach ($a as &$v) { } // 4th iteration $v is a reference to $a[3] ('d')
// At the end of the foreach loop,
// $v is still a reference to $a[3] ('d')
foreach ($a as $v) { } // 1st iteration $v (still a reference to $a[3])
// is set to a value of $a[0] ('a').
// Because it is a reference to $a[3],
// it sets $a[3] to 'a'.
foreach ($a as $v) { } // 2nd iteration $v (still a reference to $a[3])
// is set to a value of $a[1] ('b').
// Because it is a reference to $a[3],
// it sets $a[3] to 'b'.
foreach ($a as $v) { } // 3rd iteration $v (still a reference to $a[3])
// is set to a value of $a[2] ('c').
// Because it is a reference to $a[3],
// it sets $a[3] to 'c'.
foreach ($a as $v) { } // 4th iteration $v (still a reference to $a[3])
// is set to a value of $a[3] ('c' since
// the last iteration).
// Because it is a reference to $a[3],
// it sets $a[3] to 'c'.
Upvotes: 116