Stop Writing Recursive Directory Functions Use RecursiveDirectoryIterator Instead cover image

Stop Writing Recursive Directory Functions Use RecursiveDirectoryIterator Instead

Scott Keck-Warren • June 5, 2026

A few years back, I spent an entire afternoon debugging a recursive function that was supposed to scan a project directory and list every PHP file. It worked great on small folders. Then I pointed it at a real project with nested directories, symlinks, and thousands of files. The function hit the stack limit and crashed. I just sat there staring at a "Maximum function nesting level reached" error, trying to figure out what to do.

It turns out I was over complicating thnings because PHP already had this built in. It's called RecursiveDirectoryIterator, and once I found it, I stopped writing custom directory-walking code.

What an Iterator is in PHP

An iterator is an object that lets you loop through a collection of items one at a time using foreach. You don't load everything into memory at once. PHP's Standard Library (SPL) ships with a bunch of them.

The way I used to do it

Here's the kind of function I used to write. You've probably seen it.

function scanDirectory(string $path): array
{
    $results = [];
    $items = scandir($path);

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }

        $fullPath = $path . DIRECTORY_SEPARATOR . $item;

        if (is_dir($fullPath)) {
            $results = array_merge($results, scanDirectory($fullPath));
        } else {
            $results[] = $fullPath;
        }
    }

    return $results;
}

$files = scanDirectory('/var/www/myproject');

Looks fine. The problems come out when you actually use it.

Every nested directory adds another function call to the stack, so deep trees or circular symlinks will crash your script. Want only .php files? Add the filtering logic yourself, then remember to copy it every time you reuse the function. The whole thing also builds an array in memory before returning anything. You're manually skipping . and .., building full paths, managing the recursion.

RecursiveDirectoryIterator

RecursiveDirectoryIterator combined with RecursiveIteratorIterator handles all of this.

$directory = new RecursiveDirectoryIterator(
    'vendor',
    RecursiveDirectoryIterator::SKIP_DOTS // Skip . and .. automatically
);

$iterator = new RecursiveIteratorIterator(
    $directory,
    RecursiveIteratorIterator::SELF_FIRST // Include directories in results too
);

foreach ($iterator as $file) {
    echo $file->getPathname() . PHP_EOL;
}

The iterator traverses internally and yields one file at a time, so memory stays low and no stack overflows unexpectedly.

$file in that loop is a SplFileInfo object which comes with some fun helper functions and no string wrangling.

Filtering with RegexIterator

Want only .php files? Wrap it in a RegexIterator.

$directory = new RecursiveDirectoryIterator(
    'vendor',
    RecursiveDirectoryIterator::SKIP_DOTS
);

$iterator = new RecursiveIteratorIterator($directory);

// Only match files ending in .php
$phpFiles = new RegexIterator($iterator, '/\.php$/i');

foreach ($phpFiles as $file) {
    echo $file->getPathname() . PHP_EOL;
}

/\.php$/i matches paths ending in .php. Swap in whatever you need: /\.log$/, /\.blade\.php$/, /\.(jpg|png|gif)$/i. Filtering happens as the iterator runs, so you never build an unfiltered array first.

A conditional in the loop works just as well if regex feels like overkill.

foreach ($iterator as $file) {
    if ($file->isFile() && $file->getExtension() === 'php') {
        echo $file->getPathname() . PHP_EOL;
    }
}

Pick whichever one you'll still understand six months from now.

PHP's standard library is underused. Next time you reach for a recursive scandir function, use this instead.