php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #70812 Nested foreach with iterators
Submitted: 2015-10-29 13:49 UTC Modified: 2015-10-30 10:32 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:0 (0.0%)
From: fra dot casula at gmail dot com Assigned:
Status: Not a bug Package: *General Issues
PHP Version: 5.6.14 OS: Ubuntu 15.10 x64
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: fra dot casula at gmail dot com
New email:
PHP Version: OS:

 

 [2015-10-29 13:49 UTC] fra dot casula at gmail dot com
Description:
------------
A nested foreach on the same iterator doesn't produce the same result that is produced when using plain arrays.

The nested foreach should call seek() to reset the internal pointer to its correct position so that the first foreach can resume where it stopped.

Test script:
---------------
http://pastebin.com/EQrUZ7AQ

Expected result:
----------------
I expect to see the same output for all four cases in the example code. The expected output is:

first loop 0
second loop 0
second loop 1
second loop 2
first loop 1
second loop 0
second loop 1
second loop 2
first loop 2
second loop 0
second loop 1
second loop 2

Actual result:
--------------
first loop 0
second loop 0
second loop 1
second loop 2

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-10-29 15:41 UTC] bwoebi@php.net
-Status: Open +Status: Not a bug
 [2015-10-29 15:41 UTC] bwoebi@php.net
The difference is that arrays are by-value — they have all own copies which are all individually iterated. [At least that's how they function semantically.]
Iterators though are objects and thus share a common state.

It's not like arrays would be seeking each time and iterators not. Just each foreach works on its own array copy, but shares the object.
 [2015-10-29 17:09 UTC] fra dot casula at gmail dot com
I get what you're saying, arrays are by value. Objects, starting from PHP5, are by reference. Still if I unset an element of the same array in the nested foreach I get that element wiped in the main array as well.

$a = [1, 2, 3, 4];

foreach ($a as $i => $v) {
    foreach ($a as $i2 => $v2) {
        if ($i2 % 2 == 0) {
            unset($a[$i2]);
        }
    }
}

var_dump($a); // it prints 2, 4 (of course)

If each foreach is working with its own array copy like you said then there's probably a scope issue?
The whole thing is confusing because someone would think that iterators work just like a normal array but then, even if we don't implement a thing and use the ready-to-use ArrayIterator class, we get a completely different behaviour with a simple thing like a nested foreach. It's not just a completely different behaviour, it's just unreliable. The ArrayIterator class, as it is, can't even be used in a nested foreach unless we start setting positions manually. That would probably make us loose the benefit of using iterators in the first instance.
 [2015-10-30 07:11 UTC] a at b dot c dot de
When you unset $a[$i2], you're removing the element from $a, not from the copy being iterated over. Each loop starts by getting a copy of $a as it exists at that moment.

$a = [1,2,3,4];
foreach ($a as $i => $v) {
	echo 'outer loop: ', $v, ' ', join(",", $a),"\n";
    foreach ($a as $i2 => $v2) {
		echo 'inner loop: ', $v2, ' ', join(",", $a),"\n";
        if ($i2 % 2 == 0) {
			echo "Drop $i2\n";
            unset($a[$i2]);
        }
    }
}
 [2015-10-30 10:32 UTC] fra dot casula at gmail dot com
That's all right because it's how arrays are meant to work. Most languages out there work this way. The problem is that a feature has been added and it doesn't work as anyone would assume. A language feature should be there to solve a problem and make developers' lives easier. Instead, in this specific case, I think it just makes applications written with iterators error-prone. It also add a complexity that in my opinion make loose the benefit of using iterators in the first instance. A similar thing happens if you unset an element in one of the two foreach loops. The internal pointer is incremented, then you reach the end of the foreach loop where you unsetted the element and the internal pointer gets incremented again. Outcome is that the parent loop skips an element. Isn't all this at least worth a warning in the docs in your opinion?
 
PHP Copyright © 2001-2026 The PHP Group
All rights reserved.
Last updated: Tue Oct 06 04:00:02 2026 UTC