Unexpected behaviour of current() in a foreach loop

This question already has an answer here:

  • How does PHP 'foreach' actually work? 7 answers

  • Why does it start with B?

    Since 5.2 foreach (reliably) advances the array pointer before the loop body starts. See also the FE_RESET opcode.

    $list = array("A", "B", "C","D");
    foreach ($list as $var) {
        break;
    }
    var_dump(current($list));
    

    Output:

    B
    

    This may have something to with how the ZEND_OP_DATA pseudo opcode works (which isn't really documented).

    Why does current() keep giving the same value?

    Before the loop starts, foreach creates an internal reference to the array that you're looping over. Once inside the loop, whenever the array variable is modified or passed by reference, the internal reference is disassociated from the variable by making a copy of the array structure (but not the elements). This copied value retains the array pointer (which had earlier been modified by the loop initialization).

    This behaviour is also exhibited with a more destructive unset() operation:

    $list = array('A', 'B', 'C', 'D');
    foreach ($list as $key => $val) {
      echo $val;
      unset($list[1], $list[2], $list[3]);
    }
    echo "n", print_r($list, true), "n";
    

    Output:

    ABCD
    Array
    (
        [0] => A
    )
    

    Passing loop variable to a function

    This is another interesting scenario:

    $list = array('A', 'B', 'C', 'D');
    function itm($arr) 
    {
        return current($arr);
    }
    
    foreach ($list as $item) {
        print itm($list);
    }
    var_dump(current($list));
    

    Output:

    BCDA
    bool(false)
    

    This time, the array is passed by value and thus its array structure is copied (not the elements) into the function's $arr parameter. Unlike the previous example, there's no disassociation between the loop's internal reference and the $list symbol because the copy takes place in the function scope.

    What about the last "A" ?

    This is by far the most mystifying behaviour of foreach and can only be witnessed under these circumstances. In the last loop iteration, the array pointer is seemingly rewound to the first item; seemingly because at the end of the loop it obviously points beyond the end of the elements (as you can see from the last line of the output).

    This may have something to do with the SWITCH_FREE opcode that's executed at the end of a foreach .

    So why does placing foreach in a function make it different?

    Observe the following code:

    function item2($arr) 
    {
        foreach ($arr as $var) {
            print(current($arr));
        }
        var_dump(current($arr));
    }
    $list = array("A","B","C","D");
    item2($list);
    

    Output:

    AAAA
    string(1) "A"
    

    In this case, the internal reference of the foreach is initialized with a copy of the array (because it has a refcount > 1) and thus creates an immediate disassociation from the $arr symbol.

    Can it get worse?

    Of course! You can get even whackier results when you start using references or nest multiple foreach loops on the same variable.

    So how can I get consistent results?

    Use Iterators or don't rely on getting a consistent value from referencing the array variable during a foreach operation.


    FROM PHP.net

    The current() function simply returns the value of the array element that's currently being pointed to by the internal pointer. It does not move the pointer in any way

    then: use next()

    $list = array("A", "B", "C","D");
    foreach ($list as $var) {
        print(current($list));
        next($list);
    }
    

    NOTE: the first element will not be printed because foreach moved the pointer to second element of the array :)

    This example will explain the full behaviour:

    $list = array("A", "B", "C","D");
    foreach ($list as $var) {
       if(!isset($a)) reset($list); $a = 'isset';
       print(current($list));
       next($list);
    }
    

    out put is ABCD

    Please also note that:

    As foreach relies on the internal array pointer changing it within the loop 
    may lead to unexpected behavior.
    

    foreach


    EDIT: I want to share also my new confusing finding!!!

    Example1:

    $list = array("A", "B", "C","D");
    $list_copy = $list;
    foreach ($list as $key => $val) {
      current($list_copy);
      echo current($list);
      //next($list);
    }
    

    OUTPUT: AAAA

    Example2:

    $list = array("A", "B", "C","D");
    $list_copy = $list;
    foreach ($list as $key => $val) {
      current($list_copy);
      echo current($list);
      next($list);
    }
    

    OUTPUT: ABCD

    When calling current() function inside foreach even for another array it will affect the foreach behavior...

    Example3:

    $list = array("A", "B", "C","D");
    
    $refcopy = &$list;
    
    foreach ($list as $key => $val) {
      if(!isset($a)) { $a = 'isset'; reset($list); }
      echo current($list);
      next($list);
    }
    

    OUTPUT: ACD (WOW! B is missing)

    Example: 4

    $list = array("A", "B", "C","D");
    
    $refcopy = &$list;
    
    foreach ($list as $key => $val) {
      echo current($list);
      next($list);
    }
    

    OUTPUT: BCD

    It can't be decided exactly what will happen inside foreach loop!!!


    Well I have no real clue as to why this is, but i suspect it might have something to do with how the assignment is evaluated/processed. For fun I tried this and it resulted in another incorrect behaviour:

    $arr = array('A', 'B', 'C', 'D');
    function itm($val) {
        return current($val);
    }
    
    foreach ($arr as $item) {
        print itm($arr);
    }
    

    Result: BCDA

    So my guess is that whats happening here is that the function call forces the evaluation of current to happen in a ~correct manner. Also the reason for me getting BCDA instead of ABCD is probably because the internal pointer at first is incremented (pointing at B) and then in the en it is reset back to point at A.

    It might be worth noting this line in the PHP doc:

    Note that the assignment copies the original variable to the new one (assignment by value), so changes to one will not affect the other. This may also have relevance if you need to copy something like a large array inside a tight loop.

    I guess this doesn't really count as an answer but I liked your question and wanted to contribute a little.

    链接地址: http://www.djcxy.com/p/53024.html

    上一篇: 在PHP的foreach循环中增加数组元素?

    下一篇: foreach循环中current()的意外行为