Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecursiveIteratorIterator returns extra elements

Tags:

iterator

php

RecursiveIteratorIterator returns extra result if rewind() is not called before while loop

Example

$array = array("A","B","C");
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
//$iterator->rewind() ; this would fix it 
while ( $iterator->valid() ) {
    print($iterator->current()) ;
    $iterator->next();
}

Output

AABC  <--- Instead of ABC
  • Why an extra A not C ?
  • The Array has never been initiated or called why is $iterator->rewind() required for while loop
  • foreach works perfectly without having to call rewind whats the differences between foreach and while when working with iterators

Code In action

like image 750
Baba Avatar asked Nov 25 '12 21:11

Baba


1 Answers

I'm going to answer the questions in reverse order:

foreach works perfectly without having to call rewind whats the differences between those foreach and while when working with iterators

foreach internally does a call to rewind(), that's why you don't have to do it yourself. This is always done, so even if you have already used an iterator, the foreach loop will start over from the beginning. (You can avoid this by wrapping it in a NoRewindIterator).

The Array has never been initiated or called why is $iterator->rewind() required for while loop

The SPL iterators are designed to be used with foreach and to avoid duplicate method calls in this case. If the RecursiveIteratorIterator would call the RecursiveArrayIterator::rewind() method on construction, then it would be called again when the foreach loop starts. That's why the call isn't done.

Why an extra A not C ?

To figure this out it is nice to see which methods of the RecursiveArrayIterator actually get called:

<?php

class DebugRAI extends RecursiveArrayIterator {
    public function rewind() { echo __METHOD__, "\n"; return parent::rewind(); }
    public function current() { echo __METHOD__, "\n"; return parent::current(); }
    public function key() { echo __METHOD__, "\n"; return parent::key(); }
    public function valid() { echo __METHOD__, "\n"; return parent::valid(); }
    public function next() { echo __METHOD__, "\n"; return parent::next(); }
}

$array = array("A", "B", "C");
$iterator = new RecursiveIteratorIterator(new DebugRAI($array));
while ($iterator->valid()) {
    echo $iterator->current(), "\n";
    $iterator->next();
}

This produces the following output:

DebugRAI::valid
DebugRAI::current
A
DebugRAI::valid
DebugRAI::valid
A
DebugRAI::next
DebugRAI::valid
DebugRAI::valid
DebugRAI::current
B
DebugRAI::next
DebugRAI::valid
DebugRAI::valid
DebugRAI::current
C
DebugRAI::next
DebugRAI::valid
DebugRAI::valid

The output looks a bit odd, in particular the second iteration misses the next() call, so it just stays at the same element.

The reason for this is a peculiarity in the RecursiveIteratorIterator implementation: Iterators start off in the RS_START state and the first next call in this state only checks hasChildren(), but does not actually call the underlying iterator's next() method. After this was done it switches into the RS_NEXT mode in which the next() call happens properly. That's why the forward move is delayed by one step.

In my eyes this is a bug, but https://bugs.php.net/bug.php?id=44063 claims otherwise.

like image 149
NikiC Avatar answered Oct 16 '22 22:10

NikiC