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
// 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');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
$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
$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
$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
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
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 thanscandir()when you only want files matching a pattern.RecursiveDirectoryIteratorhandles the recursion for you when tree-walking logic grows complex.