Tips & Tricks

Beware of foreach Loop in PHP

You must be surprised/scared after reading title of this article. But I would say yes, foreach loop can create very confusing bug in your program/application if it has not been used in proper way. I will demonstrate the same in this article.

P.S.: This article does to mean to say foreach loop always creates problem, but it would create problem if not used in proper manner.

php foreach bug

Let’s move to practical stuff to see this problem in action, please have a look at below code block:

[cc lang=”php”]
$arr = [1, 2, 3]; // PHP 5.4 Only
echo implode(‘,’, $arr), “\n”;

foreach ($arr as $value) {}
echo implode(‘,’, $arr), “\n”;

foreach ($arr as $value) {}
echo implode(‘,’, $arr), “\n”;
[/cc]

[gads]

Above code will give you following output.

[cc lang=”php”]
1,2,3
1,2,3
1,2,3
[/cc]

Upto now foreach seems fine and nothing unusual happens. But now let’s see some unusal things with foreach now, writing same code again with just (very)minor change in it.

[cc lang=”php”]
$arr = [1, 2, 3]; // PHP 5.4 Only
echo implode(‘,’, $arr), “\n”;

foreach ($arr as &$value) {} // Reference
echo implode(‘,’, $arr), “\n”;

foreach ($arr as $value) {} // Value
echo implode(‘,’, $arr), “\n”;
[/cc]

You must have guessed that it will give you same output but infact it is not true. It will give you following output.

[cc lang=”php”]
1,2,3
1,2,3
1,2,2
[/cc]

Now you must be surprised and having question of WHY that happens. Let’s go through each step of above code which shows the real case.

Step 1:

Here array is declared and echoed by imploding array elements. So here nothing changed to array.

Step 2:

Here we have foreach loop, you must have noted [code]&[/code] in front of [code]$value[/code], which means we are passing each element of [code]$arr[/code] with reference instead of just value. So for each iteration we are assigning reference to array element. Once this iteration is completed $value will have reference to last element of [code]$arr[/code] which is [code]$arr[2][/code] and value is [code]3[/code].

Step 3:

When this foreach starts [code]$value[/code] holds [code]3[/code] which is reference to [code]$arr[2][/code]. This loop will create a confusing issue specially when accessing [code]$value[/code] by value instead of reference. We will go through step of step each iteration.

  1. 1st Pass: This will copies [code]$arr[0][/code] (i.e., “1”) into [code]$value[/code] which is a reference to [code]$arr[2][/code], so [code]$arr[2][/code] now equals [code]1[/code]. So [code]$arr[/code] now contains [code][1, 2, 1][/code].
  2. 2nd Pass: This will copies [code]$arr[1][/code] (i.e., “2”) into [code]$value[/code] which is a reference to [code]$arr[2][/code], so [code]$arr[2][/code] now equals [code]2[/code]. So [code]$arr[/code] now contains [code][1, 2, 2][/code].
  3. 3rd Pass: This will copies [code]$arr[2][/code] (which now equals “2”) into [code]$value[/code] which is a reference to [code]$arr[2][/code], so [code]$arr[2][/code] still equals [code]2[/code]. So $arr now contains [code][1, 2, 2][/code].

[gads]

Now you should have clear idea on why does that wierd thing happen with foreach loop. This bug was already reported back in 2004 (https://bugs.php.net/bug.php?id=29992), but this has not been considered as BUG.

Solution

So what can be the resolution for this issue? Its very simple, just remove reference which is assigned to [code]$value[/code], just have a look at below code block.

[cc lang=”php”]
$arr = [1, 2, 3]; // PHP 5.4 Only
echo implode(‘,’, $arr), “\n”;

foreach ($arr as &$value) {} // Reference
echo implode(‘,’, $arr), “\n”;

unset($value); // This will remove reference to last element

foreach ($arr as $value) {} // Value
echo implode(‘,’, $arr), “\n”;

/* Output */

1,2,3
1,2,3
1,2,3
[/cc]

So this was the weird stuff I have found with foreach loop in PHP. Look forward to your comments on this.

Shares:
  • Avinash
    August 10, 2014 at 8:41 am

    more worst case

    
    
    $arr = [1, 2, 3]; // PHP 5.4 Only
    $arr2 = [4, 5, 6];
    
    echo implode(',', $arr), "n";
    
    foreach ($arr as &$value) {} // Reference
    echo implode(',', $arr), "n";
    
    foreach ($arr2 as $value) {} // Value
    echo implode(',', $arr), "n";
    // Output:
    1,2,3
    1,2,3
    1,2,6
    
    
    
    Reply
  • Divy Singh Rathore
    Divy Singh Rathore
    August 16, 2014 at 4:16 pm

    But In scope of foreach loop your your not printing the $arr and not even replacing values of $arr. So it should not because of foreach.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *