Strange behavior of foreach when using reference: foreach ($a as &$v) { ... }
Asked Answered
M

3

55
<?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?

Magistracy answered 11/2, 2011 at 12:58 Comment(8)
What does "i think, its normal prog but web i saw op.........then ....ahh wat is it" mean?Addition
i'm wondering!!! Why get this result?? :)Magistracy
@Bart Kiers: He seems in a daze.Amador
possible duplicate of Unintentional destruction of php array element?Pliers
This is the best explanation for this behavior: schlueters.de/blog/archives/141-References-and-foreach.htmlLambdoid
possible duplicate of Strange behaviour after loop by reference - Is this a PHP bug?Mcquiston
This question is now discussed on Meta.Raynata
The PHP manual recommends calling unset($v); after the first foreach loop to avoid this problem. php.net/manual/en/control-structures.foreach.phpJaborandi
E
115

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'.
Exotoxin answered 11/2, 2011 at 13:2 Comment(6)
@Manish Trivedi: See the Warning part for why this is happening. There's nothing wrong with your program.Amador
Very good explanation! Thank you :D I have to remember that one as a reference/dupe, all other answers about reference with foreach doesn't explain it as good as you did here!Mauldon
In your unset($v);, why is $v accessible outside the scope of foreachs? :oJuncaceous
@Juncaceous - don't understand the question.... PHP local variable scope is within a function, not simply within a loopExotoxin
@MarkBaker thank you. You answered my question. Just coming to grips with PHP idiosyncrasies :)Juncaceous
Great answer! Glad you mentioned the workaround of adding unset($v); after the first foreach loop.Jaborandi
P
3

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.

Piteous answered 3/7, 2015 at 9:25 Comment(0)
R
-1

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!

Robbirobbia answered 22/9, 2023 at 7:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.