Unexpected behaviour of current() in a foreach loop
This question already has an answer here:
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