Bug #70812 [Nab]: Nested foreach with iterators

From: Date: Fri, 30 Oct 2015 10:32:55 +0000
Subject: Bug #70812 [Nab]: Nested foreach with iterators
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-196920@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=70812&edit=1

 ID:                 70812
 User updated by:    fra dot casula at gmail dot com
 Reported by:        fra dot casula at gmail dot com
 Summary:            Nested foreach with iterators
 Status:             Not a bug
 Type:               Bug
 Package:            *General Issues
 Operating System:   Ubuntu 15.10 x64
 PHP Version:        5.6.14
 Block user comment: N
 Private report:     N

 New Comment:

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?


Previous Comments:
------------------------------------------------------------------------
[2015-10-30 07:11:43] 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-29 17:09:00] 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-29 15:41:21] 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 13:49:41] 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


------------------------------------------------------------------------



--
Edit this bug report at https://bugs.php.net/bug.php?id=70812&edit=1


Thread (5 messages)

« previous php.bugs (#196920) next »