0

File locking — flock() for concurrent write safety

Advanced5 min read·php-13-007
performance

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 for rmdir).

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
<?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');