0

I have some unexpected behavior with references:

foreach ($this->data as &$row)
{
        $row['h'] = 1;
}

foreach ($this->data as $id => $row)
{
     ... in some cases $row[$id] = $row;
}

The result is that the last element of the array is replaced with the second to last element of the array. It is fixed with the following code:

foreach ($this->data as $key => $row)
{
     $this->data[$key]['h'] = 1;
}

Unfortunately, I don't have more time to spend on this. Maybe it is an error with PHP (PHP 5.5.9-1ubuntu4) or something I don't know about references?

user1122069
  • 1,767
  • 1
  • 24
  • 52

2 Answers2

1

There is a perfectly logical explanation and this is not a bug!

PHP 5 introduces the possibility of modifying the contents of the array directly by assigning the value of each element to the iterated variable by reference rather than by value. Consider this code, for example:

$a = array (’zero’,’one’,’two’);

foreach ($a as &$v) {
}
foreach ($a as $v) {
}

print_r ($a);

It would be natural to think that, since this little script does nothing to the array, it will not affect its contents... but that’s not the case! In fact, the script provides the following output:

Array
(
[0] => zero
[1] => one
[2] => one
)

As you can see, the array has been changed, and the last key now contains the value ’one’. How is that possible? The first foreach loop does not make any change to the array, just as we would expect. However, it does cause $v to be assigned a reference to each of $a’s elements, so that, by the time the loop is over, $v is, in fact, a reference to $a[2].

As soon as the second loop starts, $v is now assigned the value of each element. However, $v is already a reference to $a[2]; therefore, any value assigned to it will be copied automatically into the last element of the arrays! Thus, during the first iteration, $a[2] will become zero, then one, and then one again, being effectively copied on to itself. To solve this problem, you should always unset the variables you use in your by-reference foreach loops—or, better yet, avoid using the former altogether.

merl
  • 38
  • 7
0

When looping over an array by reference, you need to manually let go of the reference at the end of your for loop to avoid weird behaviors like this one. So your first foreach should be:

foreach ($this->data as &$row)
{
    .... code ....
}
unset($row);

In this case, unset is only destroying the reference, not the contents referenced by $row.

See the warning in the PHP foreach documentation

hlscalon
  • 7,304
  • 4
  • 33
  • 40
charmeleon
  • 2,693
  • 1
  • 20
  • 35