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. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

You May Also Like…

Squarespace Image Export

To gain continued access to your Squarespace website images after cancelling your subscription you have several...

MySQL 8.x GRANT ALL STATEMENT

-- CREATE CREATE USER 'tgnrestoreuser'@'localhost' IDENTIFIED BY 'AppleSauceLoveBird2024'; GRANT ALL PRIVILEGES ON...

Exetel Opt-Out of CGNAT

If your port forwards and inbound and/or outbound site-to-site VPN's have failed when switching to Exetel due to their...