W3docs

PHP Directory

PHP Directory: A Comprehensive Reference Guide for PHP developers.

PHP ships with a set of built-in directory functions that let your scripts inspect, create, read, and remove folders on the server's filesystem — without shelling out to the operating system. This page explains what each core function does, when to reach for it, and the common mistakes to avoid.

This is a companion to PHP File Handling and the broader PHP Filesystem reference: files live inside directories, so the two topics are almost always used together.

Why directory operations matter

Almost every non-trivial application touches the filesystem. You need directory functions to:

  • Organize uploaded files — sort user uploads into per-user or per-date folders (see PHP File Upload).
  • Create project structure on the fly — generate cache, log, or export folders the first time they're needed.
  • Iterate over content — list every template, image, or data file in a folder to process it in bulk.
  • Clean up — delete temporary directories once a job finishes.

Because these functions are part of the PHP core, they work the same on Linux, macOS, and Windows (with / accepted as the separator everywhere), which makes them more portable than running mkdir or ls through exec().

Core directory functions

FunctionPurpose
mkdir($path, $mode, $recursive)Create a directory. Pass true for $recursive to create nested parents.
rmdir($path)Remove a directory — it must be empty first.
is_dir($path)Return true if the path exists and is a directory.
scandir($path)Return an array of every entry in a directory (including . and ..).
opendir() / readdir() / closedir()Open a directory handle and read entries one at a time.
getcwd()Return the current working directory.
chdir($path)Change the current working directory.

scandir() vs. opendir()

Both list a directory's contents, but they suit different needs:

  • scandir() loads every entry into an array at once. It's concise and easy to sort, but uses memory proportional to the number of files. Best for small-to-medium folders.
  • opendir() / readdir() stream entries one by one. This keeps memory flat even for directories with tens of thousands of files. Best for very large folders.

Both include the special . (current) and .. (parent) entries — you almost always want to skip those.

Example: create a directory and list it

This script checks whether a directory exists, creates it (along with any missing parent folders) if not, then prints its contents:

<?php
$dir = 'uploads/images';

// Create the directory and any missing parents if it doesn't exist
if (!is_dir($dir)) {
    mkdir($dir, 0755, true);
    echo "Directory created: $dir\n";
}

// List directory contents, skipping the . and .. entries
$files = scandir($dir);
echo "Contents of $dir:\n";
foreach ($files as $file) {
    if ($file !== '.' && $file !== '..') {
        echo "- $file\n";
    }
}
?>

The third argument to mkdir() (true) is the recursive flag: without it, mkdir('uploads/images') fails if uploads doesn't already exist. The 0755 is an octal permission mode (owner can read/write/execute, others can read/execute) and is ignored on Windows.

Example: streaming with opendir()

For large directories, read entries one at a time instead of building a full array:

<?php
$dir = __DIR__;

if ($handle = opendir($dir)) {
    while (($entry = readdir($handle)) !== false) {
        if ($entry !== '.' && $entry !== '..') {
            $type = is_dir("$dir/$entry") ? 'dir ' : 'file';
            echo "[$type] $entry\n";
        }
    }
    closedir($handle);
}
?>

Note the strict !== false comparison: a file literally named "0" is falsy, so a loose while ($entry = readdir(...)) would stop early. Always compare against false explicitly.

Removing directories

rmdir() only deletes an empty directory. To remove one that still holds files, delete the contents first — typically with a recursive helper:

<?php
function removeDir(string $dir): void {
    foreach (scandir($dir) as $entry) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }
        $path = "$dir/$entry";
        is_dir($path) ? removeDir($path) : unlink($path);
    }
    rmdir($dir);
}
?>

This walks each entry, recursing into sub-directories and unlink()-ing files, so the directory is empty by the time rmdir() runs.

Common gotchas

  • mkdir() without the recursive flag fails if any parent folder is missing. Pass true when creating nested paths.
  • Permissions are subtracted by umask — the actual mode is $mode & ~umask(), so 0777 may land as 0755.
  • rmdir() on a non-empty folder returns false and emits a warning. Empty it first.
  • scandir() includes . and .. — filter them out before processing.
  • Relative paths depend on getcwd(), which is the script's working directory, not the file's location. Use __DIR__ to anchor paths to the current file.

Directory workflow at a glance

graph TD
A[Check Directory] --> B{Exists?}
B -->|No| C[mkdir]
B -->|Yes| D[scandir / opendir]
C --> E[Create Subdirectories]
D --> F[Read Entries]
F --> G[Process Files]
G --> H[close / rmdir]

Summary

PHP's directory functions give you a portable, built-in way to manage folders: mkdir() and rmdir() create and remove them, is_dir() checks them, and scandir() or opendir()/readdir() list their contents. Reach for scandir() on small folders for its simplicity, and opendir() streaming on large ones to keep memory flat. Combine these with PHP file functions and file handling to build robust upload, cache, and export features.

Practice

Practice
What functionality does PHP directory provide?
What functionality does PHP directory provide?
Was this page helpful?