Mar 8, 2015

PHP reference in foreach loop -- gotcha!

When I wrote PHP code similar to the following, I thought it was innocuous, but I was wrong.

  $arr = ['A', 'B', 'C', 'D'];

  foreach ($arr as $index => &$value) {
    $value = strtolower($value);
  }


  foreach ($arr as $index => $value) {
    . . .

  }

Besides lowercasing each string as intended, the last element of the array was overwritten by the next to last element, i.e. the array ended up with the values ['a', 'b', 'c', 'c'].

This happens consistently with any array with two or more elements. The last element is overwritten with the value preceding it.

Although I don't fully understand what's going on, I know that it has something to do with the use of the reference in the first foreach loop. After the first loop finishes executing, the variable $value continues to be a reference, and that leads to the unexpected behavior.

I have omitted the body of the second loop because it is irrelevant to the issue at hand.

There are at least three ways to fix this problem.

1)  In the second loop, use a different variable.

  foreach ($arr as $index => $item) {
    . . .

  }
 
2)  Unset the variable before using it further.

  unset($value);

3)  Use the variable as a reference also in the second loop, rather than a non-reference.

  foreach ($arr as $index => &$value) {
    . . .

  }

I don't find any of these fixes ideal, but all of them do eliminate the problem.

Source:

What References Do
http://php.net/manual/en/language.references.whatdo.php

2 comments:

  1. Do a search for "PHP foreach internal array pointer". And / or "internal array pointer" http://php.net/manual/en/control-structures.foreach.php.

    Not sure if this is related but perhaps of value?

    ReplyDelete
    Replies
    1. The article on foreach at the url you provided shows how there are a lot of complications that can arise when using references in conjunction with this looping construct.

      The particular comment by Delian Krustev proved extremely helpful. It explains how at the end of the first loop, the variable remains a reference to the last array element. In the second loop, the variable continues to refer only to the _last_ element. So as the second loop executes, array values are in turn assigned to the last element, with the final result that the last element ends up having the value of the next-to-last element.

      That was exactly the understanding I was hoping to get. Thanks!!

      Delete