1

Possible Duplicate:
Strange behavior Of foreach

Just came across this bug recently in a PHP app. Not sure what's going on.

Basically, it shows up when using a combination of two foreach (one with &, one without).

Here's a test code that reproduce the problem:

$items = array(

    array('id'=>1, 'name'=>'foo', 'value'=>150),

    array('id'=>2, 'name'=>'bar', 'value'=>190)
);


foreach($items as &$item)
{

    $item['percentage'] = $item['value'] * 0.75;

}

var_dump($items);   // All Good

foreach($items as $item)
{

    var_dump($item);    // Shows 1st item twice
}

The second foreach loop runs the block twice, as expected, but $item remains stuck on the first item.

I understand this is likely caused by the use of the reference & in the first loop but I don't see why it should behave like this..

Any idea? is that a bug?

Getting the same result on 5.3.8, 5.3.10 & 5.4

Community
  • 1
  • 1
Ben
  • 20,737
  • 12
  • 71
  • 115
  • This is a well known side effect when reusing references. I'm sure there are duplicate questions all over the place. `unset($item)` after the first loop. – deceze Apr 18 '12 at 01:49
  • Well I'm glad people rather call it a well known side effect rather than a bug :) – Ben Apr 18 '12 at 01:56
  • 1
    @FelixKling Thanks for the ref. Voting to close my own question (don't think I ever done that before) – Ben Apr 18 '12 at 01:56

5 Answers5

3

Firstly, it is not a bug as Rasmus said. See https://bugs.php.net/bug.php?id=29992

In this, right implementation of modifying array with its loop variable with &.

<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
    $value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
unset($value); // break the reference with the last element

var_dump($arr);   // All Good

foreach($arr as $value) {
   var_dump($value);    // All good
}

?>
miqbal
  • 2,213
  • 3
  • 27
  • 35
2

This is odd PHP behavior that's been around pretty much forever, and it happens when you mix the use of a variable as reference then not reference like you did.

I deal with it with a naming convention as follows: When I am using foreach with a &$item, I name it like &$refItem. This keeps me from mixing types.

dkamins
  • 21,450
  • 7
  • 55
  • 59
0

You need to unset the pointer after using foreach with a referenced array.

http://php.net/unset

dotancohen
  • 30,064
  • 36
  • 138
  • 197
  • It is right. The [PHP manual's foreach page says](http://us3.php.net/manual/en/control-structures.foreach.php) "Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().". – dotancohen Apr 18 '12 at 01:52
  • I thought you had written `reset`, not `unset`. Upvoted, now I'm going to bed.. –  Apr 18 '12 at 01:54
  • Your original answer did not say that, even the current one is ambiguous. The *array pointer* and the *reference variable* are two different things. – deceze Apr 18 '12 at 01:55
  • 1
    @TimCooper You did see `reset`, I saw it too. The answer's been updated – Ben Apr 18 '12 at 01:55
  • Yes, I did update the answer from `reset` to `unset`, but I only saw the comments after the update as I updated it literally the second after it posted. There isn't even an update log for the change! – dotancohen Apr 18 '12 at 01:59
0

This may be something that look more understand the problem of foreach

$last_value_of_first_foreach = 1;
$item = 2;
$c = 3;
$item = &$last_value_of_first_foreach ; // Think that this statement is first foreach loop
// Here $item is pointer to $last_value_of_first_foreach 
// To Better understanding, let change the name ($reference_to_last_value = $item;)

now, the new loop is

$item = $c;
// Here, what it do is update value where the $item pointer refer to 
// (mean $last_value_of_first_foreach )
// so, at here $last_value_of_first_foreach has value of $c

Now, back to your case, from the first foreach, the $item reference to the last element of array. now, when you assign something to $item in second foreach, what it do, is put something inside that one.

In the end of first loop $item is pointer to $items[1] The first of second loop it will push the first element to the location where the $item point to (that mean $items[1], so that why $items[1] is replaced by $items[0].

In case you want to prevent this one, just unset the $item variable before next time usage.

scalopus
  • 2,640
  • 3
  • 19
  • 32
0

This is a normal, not a weird behavior. Just read about reference here.

When you add & in front of a variable, you store the reference to a variable. So, when you re-use it, it will also change the contents of the referenced variable.

foreach($items as &$item) // You have $item here, prefixed with &.
{
    $item['percentage'] = $item['value'] * 0.75;
}

var_dump($items);

foreach($items as $item) // And your re-use it here.
{
    var_dump($item);
}

To solve this, add unset($item) in the 1st loop:

foreach($items as &$item)
{
    $item['percentage'] = $item['value'] * 0.75;
    unset($item); // add this, because you use &$item previously.
}
Muhammad Alvin
  • 1,190
  • 9
  • 9