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
| Function | Purpose |
|---|---|
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. Passtruewhen creating nested paths.- Permissions are subtracted by
umask— the actual mode is$mode & ~umask(), so0777may land as0755. rmdir()on a non-empty folder returnsfalseand 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.