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

8

3 Answers 3

116

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'.
6
  • 4
    @Manish Trivedi: See the Warning part for why this is happening. There's nothing wrong with your program.
    – BoltClock
    Commented Feb 11, 2011 at 13:05
  • 2
    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!
    – Rizier123
    Commented Mar 27, 2015 at 15:45
  • 4
    In your unset($v);, why is $v accessible outside the scope of foreachs? :o
    – nawfal
    Commented Nov 20, 2015 at 13:15
  • 1
    @nawfal - don't understand the question.... PHP local variable scope is within a function, not simply within a loop
    – Mark Baker
    Commented Nov 20, 2015 at 13:17
  • 2
    @MarkBaker thank you. You answered my question. Just coming to grips with PHP idiosyncrasies :)
    – nawfal
    Commented Nov 20, 2015 at 13:40
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.

-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!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.