Search code examples
phparraysforeach

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


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


Solution

  • 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'.