Written by James McDonald

May 18, 2022

Just came across a Youtube talk “Iterators in PHP” by Jake Smith published in 2014 that steps through the many different Iterators and some practical examples.

I have reproduced many of the examples Jake used below, running them with PHP 8.1.5 and published the code at https://github.com/toggenation/iterator-training

I also found a short and clearly explained video about Iterators by Dr Adam Nielson which was helpful too. Watch this one first as it’s a bit of a primer.

An Important Point About the RecursiveIterators. They require RecursiveIteratorIterator!

You can’t just use one of the Recursive*Iterators without wrapping it in RecursiveIteratorIterator if you do use it on its own it will not recurse down the tree

  • RecursiveArrayIterator
  • RecursiveCachingIterator
  • RecursiveCallbackFilterIterator
  • RecursiveDirectoryIterator
  • RecursiveFilterIterator
  • RecursiveIteratorIterator <== Wrap there rest of these with this.
  • RecursiveRegexIterator
  • RecursiveTreeIterator
<?php

$rdi = new RecursiveDirectoryIterator('/var/www/wms/webroot', FilesystemIterator::SKIP_DOTS);
$rii  = new RecursiveIteratorIterator($rdi, RecursiveIteratorIterator::CHILD_FIRST);
// now ready to walk the entire tree below the specified root
foreach ($rii as $file) {
    /**
     * @var SplFileInfo $file
     */
    echo $file->getPathname(), "\n";
}

/*

/var/www/wms/webroot/img/x-lg.svg
/var/www/wms/webroot/img/TOGGEN-LOGO.svg
/var/www/wms/webroot/img/toggen-logo-122x27.png
/var/www/wms/webroot/img/down-arrow.svg
/var/www/wms/webroot/img/100pbc-bottling-company.png
/var/www/wms/webroot/img/test-error-icon.png
/var/www/wms/webroot/img/cake.logo.svg
/var/www/wms/webroot/img/TOGGEN-GOAT.svg
/var/www/wms/webroot/img/test-fail-icon.png
/var/www/wms/webroot/img
/var/www/wms/webroot/phpinfo.php
/var/www/wms/webroot/.htaccess


 */

CachingIterator

<?php
// CachingIterator aka LookAhead Iterator
$animals = ['Cat', 'Dog', 'Shark', 'Elephant', 'Tiger', 'Lion', 'Bear', 'Donkey', 'Sheep'];

$collection = new CachingIterator(new ArrayIterator($animals));
var_dump($collection->current()); // null
var_dump($collection->getInnerIterator()->current()); // Cat

foreach ($collection as $animal) {
    echo "Current: {$animal}";

    if ($collection->hasNext()) {
        echo ' - Next:' . $collection->getInnerIterator()->current();
    }
    echo PHP_EOL;
}

/**
 * output 
 * 
 * Current: Cat - Next:Dog
 * Current: Dog - Next:Shark
 * Current: Shark - Next:Elephant
 * Current: Elephant - Next:Tiger
 * Current: Tiger - Next:Lion
 * Current: Lion - Next:Bear
 * Current: Bear - Next:Donkey
 * Current: Donkey - Next:Sheep
 * Current: Sheep
 */

CallBackIterator

<?php

$dirIt = new GlobIterator('/var/www/wms/tmp/*.csv');

$recursiveFiles = new CallbackFilterIterator(
    $dirIt,

    function ($current, $key, $it) {
        return preg_match('|test627|i', $current);
    }
);

foreach ($recursiveFiles as $name) {
    echo $name . PHP_EOL;
}

echo iterator_count($recursiveFiles) . PHP_EOL;

/**
 * output
 * /var/www/wms/tmp/TEST627b509d5ac743.01130673.csv
 * /var/www/wms/tmp/TEST627b52ac03dad4.63971256.csv
 * /var/www/wms/tmp/TEST627b53a94454a9.12415465.csv
 * /var/www/wms/tmp/TEST627b54cdee2a88.20247776.csv
 * /var/www/wms/tmp/TEST627b55a2530101.43769565.csv
 * /var/www/wms/tmp/TEST627b5643c63625.69582969.csv
 * 6
 */

DirectoryIterator

<?php

$it = new DirectoryIterator('/var/www/wms/src');

foreach ($it as $file) {
    if ($file->isDot()) {
        continue;
    }
    if ($file->isFile()) {
        echo "FILE:::: {$file->getFilename()}" . PHP_EOL;
    }

    if ($file->isDir()) {
        echo "DIR:::: {$file->getFilename()}" . PHP_EOL;
    }
    echo $file->getFilename() . PHP_EOL;
}


/**
 * output 
 * 
 * DIR:::: Command
 * Command
 * DIR:::: Log
 * Log
 * DIR:::: Routing
 * Routing
 * DIR:::: Job
 * Job
 * DIR:::: Lib
 * Lib
 * DIR:::: Model
 * Model
 * DIR:::: React
 * React
 * DIR:::: View
 * View
 * DIR:::: Middleware
 * Middleware
 * FILE:::: Application.php
 * Application.php
 * DIR:::: Listener
 * Listener
 * DIR:::: Error
 * Error
 * DIR:::: Policy
 * Policy
 * DIR:::: Console
 * Console
 * DIR:::: Form
 * Form
 * DIR:::: Controller
 * Controller
 * DIR:::: Mailer
 * Mailer
 */

FileSystemIterator

<?php


// filesystem iterator has skip dots on by default
$it = new FilesystemIterator('/var/www/wms/src');

foreach ($it as $file) {

    // not required
    // if ($file->isDot()) {
    //     continue;
    // }
    // if ($file->isFile()) {
    // echo "{$file->getFilename()}" . PHP_EOL;
    // }

    // if ($file->isDir()) {
    //     echo "DIR:::: {$file->getFilename()}" . PHP_EOL;
    // }

    echo $file->getFilename() . PHP_EOL;
}

// Output
/**
Command
Log
Routing
Job
Lib
Model
React
View
Middleware
Application.php
Listener
Error
Policy
Console
Form
Controller
Mailer
 */

LargeImageFilter and NoCVSFilter Custom Filter and RecursiveFilterIterator

<?php


class RecursiveNoVCSIterator extends RecursiveFilterIterator
{
    public function accept(): bool
    {
        $vcsFolders = ['.git', '.github', '.svn', '.vscode', '.cache'];

        /**
         * @var SplFileInfo $file File Info
         */
        $file = $this->current();


        if ($file->isDir() && in_array($file->getFilename(), $vcsFolders)) {

            return false;
        };

        return true;
    }
}



$dirs = new RecursiveDirectoryIterator('/var/www/wms', FilesystemIterator::SKIP_DOTS);

$filter = new RecursiveNoVCSIterator($dirs);

$recurseIt = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST);



// foreach ($recurseIt as $path => $info) {
//     /**
//      * @var SplFileInfo $info Info
//      */
//     echo $info->getPathname() . PHP_EOL;
// }


class LargeImageFilter extends FilterIterator

{
    private array $safeImageTypes = ['png'];
    private int $fileSize;

    public function __construct(Iterator $it, $imageTypes, $fileSize = 50000)
    {
        parent::__construct($it);

        if (!empty($imageTypes)) {
            $this->safeImageTypes = $imageTypes;
        }

        $this->fileSize = $fileSize;
    }

    public function accept(): bool
    {
        $file = $this->current();

        if (in_array($file->getExtension(), $this->safeImageTypes) && $file->getSize() > $this->fileSize) {
            return true;
        }
        return false;
    }
}


$dirs = new DirectoryIterator('/var/www/wms/webroot/files/templates');

$filter = new LargeImageFilter($dirs, [
    'png',
    'jpeg'
], 30000);

foreach ($filter as $file) {
    echo $file . ': ' . $file->getSize() . PHP_EOL;
}

echo iterator_count($filter) . PHP_EOL;


Generators

<?php

// genorator

function abc()
{
    yield 1;
    yield 2;
    yield 3;
}
echo "Generator\n";
foreach (abc() as $num) {
    echo $num . PHP_EOL;
}

// stop a generator
function rabc()
{
    yield 1;
    return; // don't use a return value
    yield 2;
    yield 3;
}
echo "Stopped Generator\n";
foreach (rabc() as $num) {
    echo $num . PHP_EOL;
}

// Output
/*
Generator
1
2
3
Stopped Generator
1
*/

GlobIterator

<?php

echo "*.php\n";
$it = new GlobIterator('/var/www/wms/webroot/*.php');;

foreach ($it as $path => $file) {
    echo $path . ' - ' . $file->getFilename() . PHP_EOL;
}


echo "*.txt\n";
$it = new GlobIterator('/var/www/wms/webroot/*.txt');;

foreach ($it as $path => $file) {
    echo $path . ' - ' . $file->getFilename() . PHP_EOL;
}

// Output

/*

*.php
/var/www/wms/webroot/index.php - index.php
/var/www/wms/webroot/info.php - info.php
/var/www/wms/webroot/phpinfo.php - phpinfo.php
*.txt
/var/www/wms/webroot/htaccess.txt - htaccess.txt

*/

InfiniteIterator

<?php


$data = [1, 2, 3];

// as it's name implies it rewinds the pointer to the start
// and keeps going forever
$it = new InfiniteIterator(new ArrayIterator($data));


$ctr = 0;

foreach ($it as $i) {
    $ctr++;

    // do this or it will go on forever
    if ($ctr === 10000) die;

    echo "{$i} - {$ctr}" . PHP_EOL;
}

// Output

/*
// ... forever...
2 - 9995
3 - 9996
1 - 9997
2 - 9998
3 - 9999

*/

LimitIterator

<?php

require '../../vendor/autoload.php';

use Faker\Factory;

$page = 5;
$perPage = 10;
$resultOffset = ($page * $perPage) - $perPage;

$faker = Faker\Factory::create();

for ($i = 0; $i < 125; $i++) {
    $words[]['name'] = $faker->word() . ' ' . $i;
}

$it = new ArrayIterator($words);

try {
    foreach (new LimitIterator($it, $resultOffset, $perPage) as $result) {
        echo "{$result['name']}\n";
    }
} catch (OutOfBoundsException $e) {
    echo "No Records Found" . PHP_EOL;
} catch (Exception $e) {
    echo $e->getMessage();
}

// Output 

/*
ullam 40
harum 41
deleniti 42
est 43
molestiae 44
aut 45
enim 46
molestias 47
placeat 48
molestiae 49
*/

Extending RecursiveIteratorIterator to create a HTML Menu

<?php

use Enqueue\Router\Recipient;

$menu = [
    'Home',
    'Register',
    'About' => [
        'The Team',
        "Our Story",
    ],
    'Contact' => [
        'Locations',
        'Support',
    ]
];


class HookRecursiveIteratorIterator extends RecursiveIteratorIterator
{
    public function beginChildren(): void
    {
        // echo __METHOD__ . PHP_EOL;
        echo '<ul data-where="beginChildren">', PHP_EOL;
    }

    public function endChildren(): void
    {
        // echo __METHOD__ . PHP_EOL;

        echo '</ul></li>', "\n";
    }
}

$it = new HookRecursiveIteratorIterator(
    new RecursiveArrayIterator($menu),
    RecursiveIteratorIterator::SELF_FIRST
);

ob_start();

echo '<ul>', "\n";

foreach ($it as $k => $v) {
    /**
     * @var RecursiveIterator $it
     */
    if ($it->hasChildren()) {
        echo "<li>{$k}\n";
        continue;
    }
    echo "<li>{$v}</li>\n";
}

echo "</ul>\n";

$html = ob_get_clean();

$config = [
    'indent'         => true,
    'output-xhtml'   => false,
    'wrap'           => 200,
    'show-body-only' => true,
    'clean' => true,
];

// Tidy
$tidy = new tidy;
$tidy->parseString($html, $config, 'utf8');
$tidy->cleanRepair();

// Output
echo $tidy . PHP_EOL;

// Output

/*

<ul>
  <li>Home
  </li>
  <li>Register
  </li>
  <li>About
    <ul data-where="beginChildren">
      <li>The Team
      </li>
      <li>Our Story
      </li>
    </ul>
  </li>
  <li>Contact
    <ul data-where="beginChildren">
      <li>Locations
      </li>
      <li>Support
      </li>
    </ul>
  </li>
</ul>

*/

ParentIterator

<?php

$rdi = new RecursiveDirectoryIterator('/var/www/wms/src');

// only iterates nodes with children
// ie. directories are parents
// in this case it iterates directories

$dirsOnly = new ParentIterator($rdi);

$iter = new RecursiveIteratorIterator(
    $dirsOnly,
    RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($iter as $dir) {
    echo $dir . PHP_EOL;
}


// Output
// Note CHILD_FIRST shows the deepest folders in the tree first

/*
/var/www/wms/src/React/shipment-app
/var/www/wms/src/React/put-away/public
/var/www/wms/src/React/put-away/src
/var/www/wms/src/React/put-away
/var/www/wms/src/React
/var/www/wms/src/View/Helper
/var/www/wms/src/View/Cell
/var/www/wms/src/View
/var/www/wms/src/Middleware/UnauthorizedHandler
/var/www/wms/src/Middleware
/var/www/wms/src/Listener
/var/www/wms/src/Error
/var/www/wms/src/Policy
/var/www/wms/src/Console
/var/www/wms/src/Form
/var/www/wms/src/Controller/Component
/var/www/wms/src/Controller
/var/www/wms/src/Mailer
*/

Using RecursiveDirectoryIterator to mass delete files and/or Folders

I have modified this so it not only deletes files but folders also. Caution you can destroy your file system if you specifiy the wrong Directory.

<?php

$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/www/wms/logs', FilesystemIterator::SKIP_DOTS),
    RecursiveIteratorIterator::CHILD_FIRST
);


// run out of memory trying to convert the iterator
// to an array
// var_dump(iterator_to_array($it));

// echo print_r(iterator_to_array($it), true);

// array_map('unlink', iterator_to_array($it));

// this allows it to run without running out of memory
foreach ($it as $k => $v) {
    if ($it->hasChildren()) {
        echo "Has Children " . $k . "\n";
        rmdir($k);
        continue;
    }
    echo "Deleting $k";
    unlink($k);
}

RecursiveTreeIterator

<?php

$it = new RecursiveDirectoryIterator('/var/www/wms/webroot', FilesystemIterator::SKIP_DOTS);

foreach (new RecursiveTreeIterator($it) as $path => $file) {

    echo $file . "\n";
}



$menu = [
    'Home',
    'Register',
    'About' => [
        'The Team',
        "Our Story",
    ],
    'Contact' => [
        'Locations',
        'Support',
    ]
];

$data = new RecursiveArrayIterator($menu);

$it = new RecursiveTreeIterator($data);

$it->setPrefixPart(
    RecursiveTreeIterator::PREFIX_LEFT,
    '//'
);

$it->setPrefixPart(
    RecursiveTreeIterator::PREFIX_RIGHT,
    '||'
);

foreach ($it as $nav) {
    echo $nav . PHP_EOL;
}


// Default output
/*
| | |-/var/www/wms/webroot/files/templates/200x150-Product-Sample-Label.png
| | |-/var/www/wms/webroot/files/templates/5-CartonLabel.txt
| | |-/var/www/wms/webroot/files/templates/Screen Shot 2022-02-11 at 11.18.47 am.png
| | |-/var/www/wms/webroot/files/templates/69-NoBarcode3DigitQtySSCCPalletLabel.txt
| | |-/var/www/wms/webroot/files/templates/200x150-Product-Sample-Label.glabels
| | \-/var/www/wms/webroot/files/templates/100x50custom-1.glabels
| |-/var/www/wms/webroot/files/edi
| | \-/var/www/wms/webroot/files/edi/send
| |   |-/var/www/wms/webroot/files/edi/send/outbound
| |   \-/var/www/wms/webroot/files/ed* i/send/inbound
| |-/var/www/wms/webroot/files/daily_reports
| | |-/var/www/wms/webroot/files/daily_reports/2022-04-_Daily_Shift_Report.pdf
| | |-/var/www/wms/webroot/files/daily_reports/2022-04-14_Daily_Shift_Report.pdf
| | |-/var/www/wms/webroot/files/daily_reports/2022-05-05_Daily_Shift_Report.pdf
| | |-/var/www/wms/webroot/files/daily_reports/20_Daily_Shift_Report.pdf
 */


// With prefixes modified
/*
/|-||Home
//|-||Register
//|-||Array
//| |-||The Team
//| \-||Our Story
//\-||Array
//  |-||Locations
//  \-||Support
*/

RegexIterator

<?php

$a = new ArrayIterator(['test1', 'test2', 'test3', 'test4', 'aaa']);

$it = new RegexIterator(
    $a,
    '/^(test)(\d)+/',
    RegexIterator::REPLACE
);

$it->replacement = '$2:$1';


foreach ($it as $el) {
    echo $el . PHP_EOL;
}

// Output

/*
1:test
2:test
3:test
4:test
*/

0 Comments

Submit a Comment

Your email address will not be published.

You May Also Like…

MacOS USB Creator

Just toasted my Windows 10 Pro install with a Windows 11 upgrade. Think it will be unrecoverable (because of Bitlocker...