The RecursiveIteratorIterator class

(PHP 5, PHP 7)

简介

Can be used to iterate through recursive iterators.

类摘要

RecursiveIteratorIterator implements OuterIterator {
/* 常量 */
const integer LEAVES_ONLY = 0 ;
const integer SELF_FIRST = 1 ;
const integer CHILD_FIRST = 2 ;
const integer CATCH_GET_CHILD = 16 ;
/* 方法 */
public beginChildren ( void ) : void
public beginIteration ( void ) : void
public callHasChildren ( void ) : bool
public __construct ( Traversable $iterator [, int $mode = RecursiveIteratorIterator::LEAVES_ONLY [, int $flags = 0 ]] )
public current ( void ) : mixed
public endChildren ( void ) : void
public endIteration ( void ) : void
public getDepth ( void ) : int
public getInnerIterator ( void ) : iterator
public getMaxDepth ( void ) : mixed
public getSubIterator ([ int $level ] ) : RecursiveIterator
public key ( void ) : mixed
public next ( void ) : void
public nextElement ( void ) : void
public rewind ( void ) : void
public setMaxDepth ([ int $max_depth = -1 ] ) : void
public valid ( void ) : bool
/* 继承的方法 */
}

预定义常量

RecursiveIteratorIterator::LEAVES_ONLY

RecursiveIteratorIterator::SELF_FIRST

RecursiveIteratorIterator::CHILD_FIRST

RecursiveIteratorIterator::CATCH_GET_CHILD

Table of Contents

User Contributed Notes

L00_Cyph3r 07-May-2019 08:02
Some update on the speed of the previous benchmark from: https://www.php.net/manual/en/class.recursiveiteratoriterator.php#112713

This benchmark if run with PHP 7.3.3 running on a 6700k@4.4GHz on Linux  5.0.4-arch1-1-ARCH #1 SMP PREEMPT

RecursiveIteratorIterator:     100 elements -> 0.006 sec
Recursive function       :     100 elements -> 0.002 sec
RecursiveIteratorIterator:    1000 elements -> 0.035 sec
Recursive function       :    1000 elements -> 0.013 sec
RecursiveIteratorIterator:   10000 elements -> 0.311 sec
Recursive function       :   10000 elements -> 0.127 sec
RecursiveIteratorIterator:  100000 elements -> 3.130 sec
Recursive function       :  100000 elements -> 1.302 sec
RecursiveIteratorIterator: 1000000 elements -> 31.223 sec
Recursive function       : 1000000 elements -> 12.749 sec
seductiveapps.com 14-Mar-2018 05:43
function &chaseToPath (&$wm, $path, $create=false) {
    //var_dump ($create); die();
    //echo '$wm=<pre>'; var_dump ($wm);echo '</pre>'; //die();
    //$path = str_replace ('/', '/d/', $path);
    //$path .= '/d';
    $nodes = explode ('/', $path);
    $chase = &chase ($wm, $nodes, $create);
   
    //echo '$wm=<pre>'; var_dump ($wm);echo '</pre>'; die();
    /*
    $dbg = array (
        '$path' => $path,
        '$nodes' => $nodes,
        '$wm' => $wm,
        '$chase' => $chase
    );
    echo '$dbg=<pre style="background:red;color:yellow;">'; var_dump ($dbg); echo '</pre>';
    */
    //die();
   
   
    $false = false;
    if (good($chase)) {
        $arr = &result($chase);   
        return $arr;
    } else return $false;
}       

function &chase (&$arr, $indexes, $create=false) {
        if (false) {
        echo 'sitewide/functions.php --- $arr=<pre>'; var_dump ($arr); echo '</pre>';
        echo 'sitewide/functions.php --- $indexes=<pre>'; var_dump ($indexes); echo '</pre>';
        echo 'sitewide/functions.php --- $create=<pre>'; var_dump ($create); echo '</pre>';
        }
    $r = &$arr;
    foreach ($indexes as $idx) {
            //echo 'sitewide/functions.php --- $idx=<pre>'; var_dump ($idx); var_dump (array_key_exists($idx,$r)); var_dump ($r); echo '</pre>';
            if (
                    is_array($r)
                    && (
                            $create===true
                            || array_key_exists($idx,$r)
                    )
            ) {
                    if ($create===true && !array_key_exists($idx,$r)) $r[$idx]=array();
                    //echo 'sitewide/functions.php --- $idx=<pre>'; var_dump ($idx); echo '</pre>';
                    $r = &$r[$idx];
            } else {
                    $err = array(
                    'msg' => 'Could not walk the full tree',
                    'vars' => array(
                            '$idx--error'=>$idx,
                            '$indexes'=>$indexes,
                            '$arr'=>$arr
                            )
                    );
                    badResult (E_USER_NOTICE, $err);
                    $ret = false; // BUG #2 squashed
                    return $ret;
            }
    }
   
        //echo 'sitewide/functions.php --- $r=<pre>'; var_dump ($r); echo '</pre>';
    return goodResult($r);
}

usage :
$nd = $newsApp2->dataSources();
//walkArray ($nd, 'walkArray_printKey', 'walkArray_printValue');
// prints the entire array

$x = chaseToPath ($nd, 'RSS_list/English News',false);
walkArray ($x, 'walkArray_printKey', 'walkArray_printValue');
// prints everything under $nd['RSS_list']['English News']
seductiveapps.com 14-Mar-2018 05:13
function walkArray (&$a, $keyCallback, $valueCallback, $k='', $level=0, $path='') {
// usage : walkArray ($someRecursiveArray, 'walkArray_printKey', 'walkArray_printValue');
// can handle recursive arrays. a nested array is a recursive array.
// is faster, especially on large arrays, than RecuriveArrayIterator, see speed testing comment at http://php.net/manual/en/class.recursiveiteratoriterator.php
// provides detailed information to callbacks on where in the data we are, something that array_walk_recursive just doesnt do.
// passes data around as pointers, not copies of data.
    if (
        !function_exists($keyCallback)
        && !function_exists($valueCallback)
    ) {
        trigger_error ('walkArray() was called without $keyCallback or $valueCallback as existing function names. see also : call_user_func() in PHP manual.', E_USER_ERROR);
        return false;
    }

    if (!is_array($a)) {
        trigger_error ('walkArray() was called but $a parameter passed is not an array.', E_USER_ERROR);
        return false;
    } else {
        foreach ($a as $k=>&$v) {
            $cd = array ( // callback data
                'type' => 'key',
                'path' => $path,
                'level' => $level,
                'k' => &$k,
                'v' => &$v
            );               
            if (!is_null($keyCallback)) call_user_func ($keyCallback, $cd);
            if (is_array ($v)) {
                walkArray ($a[$k], $keyCallback, $valueCallback, $k, $level+1, $path.'/'.$k);
            } else {
                $cd['type'] = 'value';
                if (!is_null($valueCallback)) call_user_func ($valueCallback, $cd);
            }
        }
    }
    return true;
}

function walkArray_printKey ($cd) {
    echo '<div style="background:blue;color:yellow;border-radius:5px;padding:2px;margin-top:5px;">'.PHP_EOL;
    $indent = 20 * $cd['level'];
    echo '<div style="padding-left:'.$indent.'px">'.PHP_EOL;
    echo 'key : '.$cd['k'].'<br/>'.PHP_EOL;
    echo 'path : '.$cd['path'].'<br/>'.PHP_EOL;
    echo '</div>'.PHP_EOL;
    echo '</div>'.PHP_EOL;
}

function walkArray_printValue ($cd) {
    echo '<pre style="background:green;color:white;border-radius:5px;padding:2px;margin-top:2px;">'.PHP_EOL;
    $indent = 20 * $cd['level'];
    echo '<div style="padding-left:'.$indent.'px">'.PHP_EOL;
    echo 'key : '.$cd['k'].'<br/>'.PHP_EOL;
    echo 'path : '.$cd['path'].'<br/>'.PHP_EOL;
    echo 'value : '.$cd['v'].'<br/>'.PHP_EOL;
    echo '</div>'.PHP_EOL;
    echo '</pre>'.PHP_EOL;
}
Anonymous 08-Nov-2016 10:15
A very useful use case for RecusiveIteratorIterator in combination with RecursiveArrayIterator is to replace array values on a multidimensional array at any level deep.

Usually, array_walk_recursive would be used to replace values deep within arrays, but unfortunately this only works when there is a standard key value pair - in other words, array_walk_recursive ONLY VISITS LEAF NODES, NOT arrays.

So to get around this, the iterators can be used in this way:

<?php
$array
= [
   
'test' => 'value',
   
'level_one' => [
       
'level_two' => [
           
'level_three' => [
               
'replace_this_array' => [
                   
'special_key' => 'replacement_value',
                   
'key_one' => 'testing',
                   
'key_two' => 'value',
                   
'four' => 'another value'
               
]
            ],
           
'ordinary_key' => 'value'
       
]
    ]
];

$arrayIterator = new \RecursiveArrayIterator($array);
$recursiveIterator = new \RecursiveIteratorIterator($arrayIterator, \RecursiveIteratorIterator::SELF_FIRST);

foreach (
$recursiveIterator as $key => $value) {
    if (
is_array($value) && array_key_exists('special_key', $value)) {
       
// Here we replace ALL keys with the same value from 'special_key'
       
$replaced = array_fill(0, count($value), $value['special_key']);
       
$value = array_combine(array_keys($value), $replaced);
       
// set a new key
       
$value['new_key'] = 'new value';

       
// Get the current depth and traverse back up the tree, saving the modifications
       
$currentDepth = $recursiveIterator->getDepth();
        for (
$subDepth = $currentDepth; $subDepth >= 0; $subDepth--) {
           
// Get the current level iterator
           
$subIterator = $recursiveIterator->getSubIterator($subDepth);
           
// If we are on the level we want to change, use the replacements ($value) other wise set the key to the parent iterators value
           
$subIterator->offsetSet($subIterator->key(), ($subDepth === $currentDepth ? $value : $recursiveIterator->getSubIterator(($subDepth+1))->getArrayCopy()));
       }
    }
}
return
$recursiveIterator->getArrayCopy();
// return:
$array = [
   
'test' => 'value',
   
'level_one' => [
       
'level_two' => [
           
'level_three' => [
               
'replace_this_array' => [
                   
'special_key' => 'replacement_value',
                   
'key_one' => 'replacement_value',
                   
'key_two' => 'replacement_value',
                   
'four' => 'replacement_value',
                   
'new_key' => 'new value'
               
]
            ],
           
'ordinary_key' => 'value'
       
]
    ]
];
?>

The key is in traversing back up the tree to save the changes at that level - simply calling $recursiveIterator->offsetSet(); will only set a key on the root array.
jhonatan dot teixeira at gmail dot com 04-Apr-2016 09:45
Note that this class will first iterate your entire tree and build the graph before going into your foreach. That's propably how it can iterate your leaves first or only your leaves. and that's must be the reason its slower than commom recursive functions.

Beware of that wen you have a tree that can go into a infinite loop, in some cases you know your tree has a infinite loop, but you are looking for something inside it and are breaking the loop wne found, this class wen used on these cases will get stuck into an internal infinite loop.
gerry at king-foo dot be 11-Jun-2014 12:44
Carefull when using iterator_to_array(). Because it flattens down your subiterators, elements with the same keys will overwrite eachother.

For example:

<?php

$iterator
= new RecursiveIteratorIterator(
    new
RecursiveArrayIterator([
        [
'foo', 'bar'],
        [
'baz', 'qux']
    ])
);

foreach (
$iterator as $element) {
    echo
$element;
}

?>

This will output all 4 elements as expected:

string(3) "foo"
string(3) "bar"
string(3) "baz"
string(3) "qux"

While doing:

<?php

var_dump
(iterator_to_array($iterator));

?>

will output an array with only the last 2 elements:

array(2) {
  [0]=>
  string(3) "baz"
  [1]=>
  string(3) "qux"
}
fengdingbo at gmail dot com 21-Aug-2013 02:18
if you want traversal directory。
<?php
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator("./")) as $key=>$val)
{
    echo
$key,"=>",$val,"\n";
}
?>
zlobnygrif at gmail dot com 16-Jul-2013 06:06
Some speed tests
<?php
$timer
= function ($name = 'default', $unset_timer = TRUE)
{
    static
$timers = array();
   
    if ( isset(
$timers[ $name ] ) )
    {
        list(
$s_sec, $s_mic) = explode(' ', $timers[ $name ]);
        list(
$e_sec, $e_mic) = explode(' ', microtime());
       
        if (
$unset_timer )
            unset(
$timers[ $name ] );
       
        return
$e_sec - $s_sec + ( $e_mic - $s_mic );
    }
   
   
$timers[ $name ] = microtime();
};

function
f1 ($array) {
   
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::SELF_FIRST);

    foreach (
$iterator as $key => $value ) {
        if (
is_array($value) )
            continue;
    }
}

function
f2($array) {
    foreach (
$array as $key => $value ) {
        if (
is_array($value) )
           
f2($value);
    }
}

foreach ( [
100, 1000, 10000, 100000, 1000000] as $num )
{
   
$array = [];
   
    for (
$i = 0; ++$i < $num; )
       
$array[] = [1,2,3=>[4,5,6=>[7,8,9=>10,11,12=>[13,14,15=>[16,17,18]]]]];
   
   
$timer();
   
f1($array);
   
printf("RecursiveIteratorIterator: %7d elements -> %.3f sec\n", $num, $timer());
   
   
$timer();
   
f2($array);
   
printf("Recursive function       : %7d elements -> %.3f sec\n", $num, $timer());
}

?>

Output (PHP 5.4.9-4ubuntu2.1 (cli) (built: Jun 11 2013 13:10:01))
=======================
RecursiveIteratorIterator:     100 elements -> 0.007 sec
Recursive function       :     100 elements -> 0.002 sec
RecursiveIteratorIterator:    1000 elements -> 0.036 sec
Recursive function       :    1000 elements -> 0.024 sec
RecursiveIteratorIterator:   10000 elements -> 0.425 sec
Recursive function       :   10000 elements -> 0.263 sec
RecursiveIteratorIterator:  100000 elements -> 8.153 sec
Recursive function       :  100000 elements -> 2.654 sec
RecursiveIteratorIterator: 1000000 elements -> 474.483 sec
Recursive function       : 1000000 elements -> 26.872 sec

For one million elements recursive function is more quickly!
Adil Baig @ AIdezigns 23-Jun-2011 11:17
A very important thing to note about \RecursiveIteratorIterator is that it returns a flattened array when used with the iterator_to_array function. Ex:

<?php
$arr
= array('Zero', 'name'=>'Adil', 'address' => array( 'city'=>'Dubai', 'tel' => array('int' => 971, 'tel'=>12345487)), '' => 'nothing');

$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($arr));
var_dump(iterator_to_array($iterator,true));
?>

This code will return :

array(6) {
  [0]=>
  string(4) "Zero"
  ["name"]=>
  string(4) "Adil"
  ["city"]=>
  string(5) "Dubai"
  ["int"]=>
  int(91)
  ["tel"]=>
  int(12345487)
  [""]=>
  string(7) "nothing"
}

To get the non-flattened proper array use the getArrayCopy() method, like so :

$iterator->getArrayCopy()

This will return

array(4) {
  [0]=>
  string(4) "Zero"
  ["name"]=>
  string(4) "Adil"
  ["address"]=>
  array(2) {
    ["city"]=>
    string(5) "Dubai"
    ["tel"]=>
    array(2) {
      ["int"]=>
      int(91)
      ["tel"]=>
      int(12345487)
    }
  }
  [""]=>
  string(7) "nothing"
}
Tom 06-Jan-2011 01:35
This class operates on a tree of elements, which is build by nesting recursive iterators into one another.

Thus you might say it is an iterator over iterators. While traversing those, the class pushes the iterators on a stack while traversing down to a leaf and removes them from the stack while going back up.
aidan at php dot net 29-Apr-2010 09:57
This example demonstrates using the getDepth() method with a RecursiveArrayIterator.

<?php
$tree
= array();
$tree[1][2][3] = 'lemon';
$tree[1][4] = 'melon';
$tree[2][3] = 'orange';
$tree[2][5] = 'grape';
$tree[3] = 'pineapple';

print_r($tree);
 
$arrayiter = new RecursiveArrayIterator($tree);
$iteriter = new RecursiveIteratorIterator($arrayiter);
 
foreach (
$iteriter as $key => $value) {
 
$d = $iteriter->getDepth();
  echo
"depth=$d k=$key v=$value\n";
}
?>

The output of this would be:

Array
(
    [1] => Array
        (
            [2] => Array
                (
                    [3] => lemon
                )

            [4] => melon
        )

    [2] => Array
        (
            [3] => orange
            [5] => grape
        )

    [3] => pineapple
)

depth=2 k=3 v=lemon
depth=1 k=4 v=melon
depth=1 k=3 v=orange
depth=1 k=5 v=grape
depth=0 k=3 v=pineapple
Michiel Brandenburg 14-Jun-2009 04:40
You can use this to quickly find all the files (recursively) in a certain directory. This beats maintaining a stack yourself.
<?php
$directory
= "/tmp/";
$fileSPLObjects =  new RecursiveIteratorIterator(
                new
RecursiveDirectoryIterator($directory),
               
RecursiveIteratorIterator::CHILD_FIRST
           
);
try {
    foreach(
$fileSPLObjects as $fullFileName => $fileSPLObject ) {
        print
$fullFileName . " " . $fileSPLObject->getFilename() . "\n";
    }
}
catch (
UnexpectedValueException $e) {
   
printf("Directory [%s] contained a directory we can not recurse into", $directory);
}
?>
Note: if there is a directory contained within the directory you are searching in that you have no access to read an UnexpectedValueException will be thrown (leaving you with an empty list).
Note: objects returned are SPLFileObjects
crashrox at gmail dot com 19-Dec-2008 09:51
Recursive multidimensional array flatten using SPL

<?php
function array_flatten_recursive($array) {
    if(
$array) {
       
$flat = array();
        foreach(new
RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::SELF_FIRST) as $key=>$value) {
            if(!
is_array($value)) {
               
$flat[] = $value;
            }
        }
       
        return
$flat;
    } else {
        return
false;
    }
}

$array = array(
   
'A' => array('B' => array( 1, 2, 3, 4, 5)),
   
'C' => array( 6,7,8,9)
);

print_r(array_flatten_recursive($array));
?>
-- Returns:
Array (
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
)