2

Really not sure how to even describe this, so I'll try and let the code explain.

$numbers = [
    10,
    20,
    30
];

foreach($numbers as &$number) {
    $number = 1*$number;
}

var_dump($numbers);
foreach($numbers as $number) {
    echo $number . "\n";
}

The var_dump give the following output:

array(2) {
  [0]=>
  int(10)
  [1]=>
  int(20)
  [2]=>
  &int(30)
}

but looping over the same array gives:

10
20
20

When I iterate the array, why do I get the first two elements as the numbers I defined, but the third element seems to be a copy of the second element.

Why does the var_dump have the 'expected' result, but contains a reference symbol for the third, but expected value?

Dom
  • 2,980
  • 2
  • 28
  • 41
  • 1
    This problem is documented in PHP Manual. "Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset()." https://www.php.net/manual/en/control-structures.foreach.php – user14967413 Jan 09 '21 at 23:22

1 Answers1

3

After the first loop exits, $number still exists as a reference to the last item in the array.

foreach($numbers as &$number) { ... }
// $number still "points to" $numbers[2] here

Then, when the second loop runs, you re-use $number as if it were a new variable, but it's already set as a reference:

foreach($numbers as $number) { ... }

Effectively, PHP puts the iterative values from the array into the $number variable, but the $number variable is not an empty variable -- it's basically an existing pointer to an array element, so the second loop essentially does this:

put $numbers[0] into $numbers[2]
put $numbers[1] into $numbers[2]
put $numbers[2] into $numbers[2]

You can avoid this by:

  • Using a variable other than $number for the second loop.
  • Calling unset($number) after the first loop.
  • Not using references.

I'd recommend the third option, by doing this in your first loop:

foreach ($numbers as $index => $number) {
    $numbers[$index] = 1 * $number;
}
Alex Howansky
  • 50,515
  • 8
  • 78
  • 98
  • It is also possible to use `array_walk()` instead of `foreach` to avoid propagating the `&$number` reference into the main scope: `array_walk($numbers, function (&$number) { $number = 1 * $number; });` – user14967413 Jan 09 '21 at 23:19
  • Still don't need references: `$numbers = array_map(fn($number) => 1 * $number, $numbers);` – Alex Howansky Jan 09 '21 at 23:24
  • This solution is fine, except it creates a new array (instead of editing the existing one), which could be inefficient when manipulating large arrays. – user14967413 Jan 09 '21 at 23:32
  • Thanks. I didn't understand that the $number variable in the first foreach loop was scoped outside of that loop. If I'd thought to`var_dump` the `$numbers` array after the second foreach, then there's a chance I'd have figured this one out myself! – Dom Jan 11 '21 at 11:19