File locking — flock() for concurrent write safety
Concept
PHP's SPL (Standard PHP Library) provides object-oriented filesystem iterators that make recursive directory traversal and filtered file iteration far more elegant and memory-efficient than manual recursion with scandir().
DirectoryIterator: Iterates over a single directory. Each item is a DirectoryIterator object with getFilename(), getPathname(), isFile(), isDir(), isDot(), getMTime(), getSize(), getExtension() etc.
FilesystemIterator: Like DirectoryIterator but more flexible flags. FilesystemIterator::SKIP_DOTS skips . and .. automatically. FilesystemIterator::CURRENT_AS_FILEINFO returns SplFileInfo objects (more feature-rich than DirectoryIterator).
RecursiveDirectoryIterator: Like FilesystemIterator but can descend into subdirectories when wrapped with RecursiveIteratorIterator.
RecursiveIteratorIterator: Wraps any RecursiveIterator (including RecursiveDirectoryIterator) to flatten the recursive traversal into a single loop. Traversal modes:
LEAVES_ONLY(default): Only yield files, not directories.SELF_FIRST: Yield directories before their contents.CHILD_FIRST: Yield directories after their contents (needed forrmdir).
GlobIterator: Filesystem iterator that accepts a glob pattern. new GlobIterator('/var/www/logs/*.log') — same as glob() but returns SplFileInfo objects as an iterator.
RecursiveCallbackFilterIterator: Wrap a RecursiveDirectoryIterator to filter which files/directories are included — e.g., exclude vendor/, node_modules/, .git/ during project file traversal.
Code Example
<?php
declare(strict_types=1);
// FilesystemIterator — simple directory listing
$iter = new \FilesystemIterator(
'/var/www/src',
\FilesystemIterator::SKIP_DOTS | \FilesystemIterator::KEY_AS_PATHNAME
);
foreach ($iter as $path => $info) {
/** @var \SplFileInfo $info */
echo $info->getFilename() . ' (' . $info->getSize() . " bytes)\n";
}
// RecursiveDirectoryIterator — all files recursively
$dir = new \RecursiveDirectoryIterator(
'/var/www/src',
\FilesystemIterator::SKIP_DOTS
);
$allFiles = new \RecursiveIteratorIterator($dir);
foreach ($allFiles as $file) {
/** @var \SplFileInfo $file */
if ($file->getExtension() === 'php') {
echo $file->getPathname() . "\n";
}
}
// RecursiveCallbackFilterIterator — skip vendor and node_modules
$dir = new \RecursiveDirectoryIterator('/var/www', \FilesystemIterator::SKIP_DOTS);
$filtered = new \RecursiveCallbackFilterIterator($dir, function(\SplFileInfo $current, $key, $iterator) {
$skip = ['vendor', 'node_modules', '.git', 'storage'];
if ($iterator->hasChildren() && in_array($current->getFilename(), $skip)) {
return false; // don't recurse into this directory
}
return true;
});
$files = new \RecursiveIteratorIterator($filtered);
foreach ($files as $file) {
echo $file->getPathname() . "\n";
}
// GlobIterator — find all log files
foreach (new \GlobIterator('/var/log/*.log') as $log) {
/** @var \SplFileInfo $log */
echo $log->getFilename() . ': ' . number_format($log->getSize() / 1024, 1) . "KB\n";
}
// Collect all PHP files as array
$files = iterator_to_array(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator('/var/www/src', \FilesystemIterator::SKIP_DOTS)
)
);
$phpFiles = array_filter($files, fn(\SplFileInfo $f) => $f->getExtension() === 'php');