Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behaviour of current() in a foreach loop [duplicate]

Here is a simple loop

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

Output (demo)

 BBBB   // Output for 5.2.4 - 5.5.0alpha4  BCD    // Output for 4.4.1  AAAA   // Output for 4.3.0 - 4.4.0, 4.4.2 - 5.2.3 

Question :

  • Can someone please explain whats going on ?
  • Why am i not getting ABCD
  • Even if a copy of the array was made by foreach i should be getting AAAA but not getting that in the current PHP stable version

Note* I know i can simply use print $var but the from PHP DOC

current — Return the current element in an array 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. If the internal pointer points beyond the end of the elements list or the array is empty, current() returns FALSE.

Update 1 - New Observation

Thanks to Daniel Figueroa : Just by wrapping current in a function you get different result

foreach ( $list as $var ) {     print(item($list)); }  function item($list) {     return current($list); } 

Output ( Demo )

 BCDA   // What the hell  

Question :

  • Why not getting "BBBB" ?
  • How does wrapping current in a function affect foreach output ?
  • Where did the extra "A" Come from ?

Update 2

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

Output ( See Demo )

AAAA // No longer BBBB when using a function 

Question :

  • What is the different running a loop in a function and running it outside a function because you get AAAA outside and BBBB in a function in most PHP version
like image 955
Baba Avatar asked Feb 13 '13 08:02

Baba


People also ask

How does the foreach loop work in PHP?

The PHP foreach Loop The foreach loop works only on arrays, and is used to loop through each key/value pair in an array.

Can you return from a foreach?

You can't make JavaScript's forEach() function return a custom value. Using return in a forEach() is equivalent to a continue in a conventional loop.

How foreach loop works internally in c#?

C#'s foreach loop makes it easy to iterate over elements. This loop works with any collection object that implements IEnumerable<T> . That interface defines one method: GetEnumerator() , which returns an enumerator to step through a collection of values.


1 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.

like image 134
Ja͢ck Avatar answered Oct 05 '22 16:10

Ja͢ck