1

I've encountered some strange behavior of PHP's foreach loop, which I can't explain. Maybe you can help me.

Here's a simple test script:

function mod(&$item){
    $item["position"] = preg_replace("/[^0-9]+/","",$item["position"]);
    $item["count"] = 2*$item["count"];
}

$items = [
    ["position" => "1", "count" => 4],
    ["position" => "2", "count" => 3],
];


echo "before: <br>";
foreach($items as $item){
    echo var_dump($item) . "<br>";
}

echo "modifying items...<br>";
foreach($items as &$item){
    mod($item);
}

echo "after: <br>";
foreach($items as $item){
    echo var_dump($item) . "<br>";
}


?>

The output of the script is the following:

before:
array(2) { ["position"]=> string(1) "1" ["count"]=> int(4) }
array(2) { ["position"]=> string(1) "2" ["count"]=> int(3) }
modifying items...
after:
array(2) { ["position"]=> string(1) "1" ["count"]=> int(8) }
array(2) { ["position"]=> string(1) "1" ["count"]=> int(8) } 

It seems as if the for loop has looped over item[0] twice but has left out item[1], which is very confusing. Can someone explain this behavior to me?

Thank you very much!

user3617992
  • 359
  • 1
  • 8
  • 1
    Thats why it is good practice to unset byref loop variables after the loop. Insert `unset($item)` after the second loop will solve the problem. – thehennyy Jan 03 '19 at 09:49

2 Answers2

3

Naming conflict, you're confusing php because once you iterate items with & reference and once without. The following modified test script fixes your problem:

https://3v4l.org/EhNQU

<?php

function mod(&$item){
    $item["position"] = preg_replace("/[^0-9]+/","",$item["position"]);
    $item["count"] = 2*$item["count"];
}

$items = [
    ["position" => "1", "count" => 4],
    ["position" => "2", "count" => 3],
];


echo "before: \n";
foreach($items as $item){
    var_dump($item) . "\n";
}

echo "modifying items...\n";
foreach($items as &$itemRef){
    mod($itemRef);
}
echo "after: \n";
foreach($items as $item){
    echo "A";
    var_dump($item) . "\n";
}

Generally you should avoid using the same identifier name anywhere to avoid these problems.

After a foreach loop is done, the last iterated object can still be accessed by e.g. $item. Its good practice to call unset on values iterated by reference, for more information see https://alephnull.uk/call-unset-after-php-foreach-loop-values-passed-by-reference too.

Xatenev
  • 6,383
  • 3
  • 18
  • 42
  • even though following the link and reading the explanation, I have still not understood, how the exact output is produce. Why is it that just the last item of the array is replaced by the predecessor? Anyway, at least this clarifies, why the error is happening. Thank you! – user3617992 Jan 03 '19 at 10:02
0

You are passing variable by reference and again using it in the next loop.

Try renaming the iteration variable in the second loop.

Working code:

<?php
function mod(&$item){
    $item["position"] = preg_replace("/[^0-9]+/","",$item["position"]);
    $item["count"] = 2*$item["count"];
}
$items = [
["position" => "1", "count" => 4],
["position" => "2", "count" => 3],
];
echo "before: <br>";
foreach($items as $item){
    echo var_dump($item) . "<br>";
}
echo "modifying items...<br>";
foreach($items as &$item){
    mod($item);
}
echo "after: <br>";
foreach($items as $newItem){
    echo var_dump($newItem) . "<br>";
}
?>

Output:

before:

/var/www/html/sggit/deb.php:15:
array (size=2)
  'position' => string '1' (length=1)
  'count' => int 4


/var/www/html/sggit/deb.php:15:
array (size=2)
  'position' => string '2' (length=1)
  'count' => int 3


modifying items...
after:

/var/www/html/sggit/deb.php:26:
array (size=2)
  'position' => string '1' (length=1)
  'count' => int 8


/var/www/html/sggit/deb.php:26:
array (size=2)
  'position' => string '2' (length=1)
  'count' => int 6
Pupil
  • 23,834
  • 6
  • 44
  • 66