PHP Iterators

by | May 18, 2022 | IT Tips | 0 comments

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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.