Richard Crowley’s blog

PHP references

Consider the following PHP code:

foreach ($things as $key => &$thing) { sort($thing); }
# ...
foreach ($things as $thing) { var_dump($thing); }

Two loops over the same array, separated by some amount of other code.  The first loop uses an “&” to indicate that $thing should be referenced rather than copy-on-write.

This code will result in incorrect behavior because of PHP’s relaxed notion of scopes.  The final iteration of the first loop will leave a reference to the last value in $things stored in the variable $thing.  When the second loop runs, $thing is still in scope and still a reference.  The behavior in this case is to continually overwrite the referenced value with the value at each position in $things.  The end result is a corrupted $things array in which the last two keys contain the same value and the original value at the last key has been lost.  An example:

<?php

$things = array(
    array("a" => "aa", "b" => "bb"),
    array("d" => "dd", "c" => "cc"),
    array("e" => "ee", "f" => "ff"),
);

foreach ($things as $key => &$thing) { sort($thing); }
# ...
foreach ($things as $thing) { var_dump($thing); }

Running this code produces:

array(2) {
  [0]=>
  string(2) "aa"
  [1]=>
  string(2) "bb"
}
array(2) {
  [0]=>
  string(2) "cc"
  [1]=>
  string(2) "dd"
}
array(2) {
  [0]=>
  string(2) "cc"
  [1]=>
  string(2) "dd"
}

The solution is simply to unset($thing) after the first loop so the reference doesn’t linger:

foreach ($things as $key => &$thing) { sort($thing); }
unset($thing)
# ...
foreach ($things as $thing) { var_dump($thing); }

I’ve been bitten twice by this problem.  Both times Jesse has pointed it out and both times we discussed not using PHP as a preferable solution.