PHPWorking with Directories

Working with Directories

Directories need their own set of functions in PHP, distinct from the file functions covered on earlier pages — you don't fopen() a directory to see what's inside it. This page covers creating and removing directories with mkdir()/rmdir(), listing their contents with scandir(), pattern-matching files with glob(), a brief look at the object-oriented DirectoryIterator and RecursiveDirectoryIterator, and a worked example that lists files recursively.

mkdir() and rmdir(): creating and removing directories

mkdir($path) creates a new directory, returning true on success. By default it can only create one level at a time — if the parent directory doesn't exist yet, mkdir('a/b/c') fails. Passing true as the third argument (recursive) tells it to create any missing parent directories along the way, similar to mkdir -p on the command line. rmdir($path) removes an empty directory; it refuses to delete a directory that still has files in it.

mkdir-rmdir.php

PHP
<?php

// Fails if 'cache' doesn't already exist
mkdir('cache/thumbnails');

// Creates 'cache' and 'thumbnails' if needed
mkdir('cache/thumbnails', 0755, true);

// Only succeeds if the directory is empty
rmdir('cache/thumbnails');
rmdir() won't touch non-empty directories
`rmdir()` deliberately refuses to remove a directory that still contains files or subdirectories, returning `false` and a warning instead. To delete a directory tree with contents, you need to remove every file and subdirectory first (typically by recursing through the tree, as shown further down this page) before calling `rmdir()` on the now-empty directory.
scandir(): listing directory contents

scandir($path) returns an array of every entry in a directory, including the two special entries . (the directory itself) and .. (its parent) that every directory listing on Unix-like systems contains. Filtering those out is a routine first step whenever you want just the real files and subdirectories.

scandir-example.php

PHP
<?php

$entries = scandir('uploads');

foreach ($entries as $entry) {
    if ($entry === '.' || $entry === '..') {
        continue;
    }
    echo $entry . PHP_EOL;
}
avatar.png
report.pdf
thumbnails
glob(): matching files by pattern

glob($pattern) returns an array of paths matching a shell-style wildcard pattern, without the manual filtering scandir() needs. * matches any sequence of characters within a path segment, and patterns can include a directory prefix. It reads more naturally than scandir() plus a loop whenever the goal is "give me all the .log files" rather than "give me everything."

glob-example.php

PHP
<?php

$logFiles = glob('logs/*.log');

foreach ($logFiles as $file) {
    echo $file . ' (' . filesize($file) . ' bytes)' . PHP_EOL;
}
logs/app.log (2048 bytes)
logs/errors.log (512 bytes)
DirectoryIterator and RecursiveDirectoryIterator

PHP also offers an object-oriented way to walk directories through the SPL (Standard PHP Library): DirectoryIterator gives you one object per entry, with methods like isDot(), isDir(), and getFilename() instead of raw string comparisons. RecursiveDirectoryIterator, combined with RecursiveIteratorIterator, extends that to walk an entire directory tree without you having to write the recursion by hand. They're worth reaching for once directory-walking logic gets more involved than a simple loop — filtering by file type, skipping certain subdirectories, or collecting metadata as you go.

directory-iterator-preview.php

PHP
<?php

$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('uploads', RecursiveDirectoryIterator::SKIP_DOTS)
);

foreach ($iterator as $fileInfo) {
    if ($fileInfo->isFile()) {
        echo $fileInfo->getPathname() . PHP_EOL;
    }
}
Recursively listing files with glob()

You can also build a recursive listing yourself with plain functions, which is useful when you want full control over what counts as a match. The pattern below walks a directory tree, calling itself on every subdirectory it finds and collecting matching files along the way.

recursive-listing.php

PHP
<?php

function listFilesRecursively(string $dir): array
{
    $results = [];

    foreach (scandir($dir) as $entry) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }

        $path = $dir . '/' . $entry;

        if (is_dir($path)) {
            $results = array_merge($results, listFilesRecursively($path));
        } else {
            $results[] = $path;
        }
    }

    return $results;
}

foreach (listFilesRecursively('uploads') as $file) {
    echo $file . PHP_EOL;
}
uploads/avatar.png
uploads/report.pdf
uploads/thumbnails/avatar-small.png
Directory paths built from user input need the same care as file paths
A directory name typed by a user, or a category folder chosen from a request parameter, is just as dangerous to concatenate raw into a path as a filename is — `../` sequences work the same way whether the traversal happens through a file or directory segment. Validate against a whitelist of known folder names, or resolve with `realpath()` and confirm the result stays inside the directory you intended before creating, listing, or deleting anything there.
  • mkdir($path, 0755, true) creates nested directories in one call; without the recursive flag it only creates one level.

  • rmdir() only removes empty directories — clear the contents first for a full tree deletion.

  • scandir() includes . and ..; filter them out before using the results.

  • glob() is often clearer than scandir() when you only want files matching a pattern.

  • RecursiveDirectoryIterator handles the recursion for you when tree-walking logic grows complex.

Tip
Reach for `glob()` first when you know the pattern you're after — it reads clearly and needs no manual filtering. Save the recursive `scandir()`/iterator approach for when you genuinely need to walk an entire subdirectory tree, since it's more code to get right and more places for an off-by-one or missed `.`/`..` check to slip in.