Getting a list of files in a directory is extremely easy. Traditionally, developers have used the glob() function for this purpose. To recursively get a list of all files and directories from a specific point in a directory tree is more problematic. This recipe takes advantage of an (SPL Standard PHP Library) class RecursiveDirectoryIterator, which will serve this purpose admirably.
What this class does is to parse the directory tree, finding the first child, then it follows the branches, until there are no more children, and then it stops! Unfortunately this is not what we want. Somehow we need to get the RecursiveDirectoryIterator to continue parsing every tree and branch, from a given starting point, until there are no more files or directories. It so happens there is a marvelous class, RecursiveIteratorIterator, that does exactly that. By wrapping RecursiveDirectoryIterator inside RecursiveIteratorIterator, we accomplish a complete traversal of any directory tree.
Application\Iterator\Directory class that defines the appropriate properties and constants and uses external classes:namespace Application\Iterator;
use Exception;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RecursiveRegexIterator;
use RegexIterator;
class Directory
{
const ERROR_UNABLE = 'ERROR: Unable to read directory';
protected $path;
protected $rdi;
// recursive directory iteratorRecursiveDirectoryIterator instance inside RecursiveIteratorIterator based on a directory path:public function __construct($path)
{
try {
$this->rdi = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::SELF_FIRST);
} catch (\Throwable $e) {
$message = __METHOD__ . ' : ' . self::ERROR_UNABLE . PHP_EOL;
$message .= strip_tags($path) . PHP_EOL;
echo $message;
exit;
}
}ls -l -R command. Notice that we use the yield keyword, effectively making this method into a Generator, which can then be called from the outside. Each object produced by the directory iteration is an SPL FileInfo object, which can give us useful information on the file. Here is how this method might look:public function ls($pattern = NULL)
{
$outerIterator = ($pattern)
? $this->regex($this->rdi, $pattern)
: $this->rdi;
foreach($outerIterator as $obj){
if ($obj->isDir()) {
if ($obj->getFileName() == '..') {
continue;
}
$line = $obj->getPath() . PHP_EOL;
} else {
$line = sprintf('%4s %1d %4s %4s %10d %12s %-40s' . PHP_EOL,
substr(sprintf('%o', $obj->getPerms()), -4),
($obj->getType() == 'file') ? 1 : 2,
$obj->getOwner(),
$obj->getGroup(),
$obj->getSize(),
date('M d Y H:i', $obj->getATime()),
$obj->getFileName());
}
yield $line;
}
}RegexIterator class:protected function regex($iterator, $pattern)
{
$pattern = '!^.' . str_replace('.', '\\.', $pattern) . '$!';
return new RegexIterator($iterator, $pattern);
}dir /s command:public function dir($pattern = NULL)
{
$outerIterator = ($pattern)
? $this->regex($this->rdi, $pattern)
: $this->rdi;
foreach($outerIterator as $name => $obj){
yield $name . PHP_EOL;
}
}
}First of all, we take advantage of the autoloading class defined in Chapter 1, Building a Foundation, to obtain an instance of Application\Iterator\Directory, defining a calling program, chap_02_recursive_directory_iterator.php:
define('EXAMPLE_PATH', realpath(__DIR__ . '/../'));
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
$directory = new Application\Iterator\Directory(EXAMPLE_PATH);Then, in a try {...} catch () {...} block, we make a call to our two methods, using an example directory path:
try {
echo 'Mimics "ls -l -R" ' . PHP_EOL;
foreach ($directory->ls('*.php') as $info) {
echo $info;
}
echo 'Mimics "dir /s" ' . PHP_EOL;
foreach ($directory->dir('*.php') as $info) {
echo $info;
}
} catch (Throwable $e) {
echo $e->getMessage();
}The output for ls() will look something like this:

The output for dir() will appear as follows:
