0

Directory operations: mkdir, scandir, glob, RecursiveIteratorIterator

Intermediate5 min read·php-13-003

Concept

PHP provides functions for working with directories — listing contents, creating, removing, and navigating. Understanding these is essential for file management tasks in applications.

Directory reading:

  • scandir(string $dir): Returns an array of all files and directories in $dir, sorted alphabetically. Includes . and .. entries — always filter those out.
  • opendir() / readdir() / closedir(): Low-level handle-based directory reading. readdir() returns entries one at a time; returns false when exhausted. Useful when memory matters with huge directories.
  • glob(string $pattern): Returns files matching a glob pattern (*.php, uploads/**/*.jpg). Convenient for finding files by extension or pattern. Returns empty array (not false) when nothing matches (in PHP 8.0+ with GLOB_NOCHECK, returns the pattern; without flags, returns empty array).

Directory creation and removal:

  • mkdir(string $path, int $permissions = 0777, bool $recursive = false): Creates a directory. recursive: true creates all parent directories. Permissions are modified by umask.
  • rmdir(string $path): Removes a directory. The directory must be empty — fails if it has contents.
  • unlink(string $path): Deletes a file (not directories).

Recursive removal: PHP has no built-in rm -rf. Use RecursiveDirectoryIterator + RecursiveIteratorIterator to delete contents before calling rmdir().

getcwd(): Returns current working directory. chdir(): Changes it. In web contexts, getcwd() is typically the document root. Prefer absolute paths to avoid CWD dependency.

Code Example

php
<?php
declare(strict_types=1);

// scandir — list directory contents
$entries = scandir('/var/www/uploads');
$files = array_filter($entries, fn($e) => $e !== '.' && $e !== '..' && is_file("/var/www/uploads/$e"));

// glob — find files by pattern
$phpFiles = glob('/var/www/src/**/*.php'); // may not recurse in all PHP versions
$jpegFiles = glob('/var/www/uploads/*.{jpg,jpeg,JPG}', GLOB_BRACE);
$configFiles = glob('/etc/app/config-*.json');

// mkdir with recursive creation
$uploadDir = '/var/www/storage/uploads/' . date('Y/m/d');
if (!is_dir($uploadDir)) {
    mkdir($uploadDir, 0755, recursive: true);
}
// Note: 0755 is modified by umask (typically resulting in 0644 or 0755)

// opendir — memory-efficient enumeration
$dir = opendir('/var/www/uploads');
if ($dir === false) {
    throw new \RuntimeException("Cannot open directory");
}
try {
    while (($entry = readdir($dir)) !== false) {
        if ($entry === '.' || $entry === '..') continue;
        echo "$entry\n";
    }
} finally {
    closedir($dir);
}

// Recursive directory removal
function removeDirectory(string $path): void
{
    if (!is_dir($path)) {
        throw new \InvalidArgumentException("Not a directory: $path");
    }

    $iterator = new \RecursiveIteratorIterator(
        new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
        \RecursiveIteratorIterator::CHILD_FIRST // delete children before parents
    );

    foreach ($iterator as $file) {
        if ($file->isDir()) {
            rmdir($file->getPathname());
        } else {
            unlink($file->getPathname());
        }
    }

    rmdir($path);
}

// Copy directory recursively
function copyDirectory(string $src, string $dst): void
{
    mkdir($dst, 0755, recursive: true);
    foreach (new \DirectoryIterator($src) as $item) {
        if ($item->isDot()) continue;
        $destPath = $dst . '/' . $item->getFilename();
        if ($item->isDir()) {
            copyDirectory($item->getPathname(), $destPath);
        } else {
            copy($item->getPathname(), $destPath);
        }
    }
}