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.