PHP Directory

Directory traversal, creation and removal, and recursive scanning.

Intermediate
Key Points`scandir` includes `.` and `..` — skip them.

Listing

PHP
<?php
$dir = __DIR__ . "/tmpdir";
@mkdir($dir);
file_put_contents($dir . "/a.txt", "a");
file_put_contents($dir . "/b.log", "b");

print_r(scandir($dir));
print_r(glob($dir . "/*.txt"));

$dh = opendir($dir);
while (($f = readdir($dh)) !== false) {
    if ($f === "." || $f === "..") {
        continue;
    }
    echo $f . "\n";
}
closedir($dh);
Output
Array
(
    [0] => .
    [1] => ..
    [2] => a.txt
    [3] => b.log
)
Array
(
    [0] => /var/www/demo/tmpdir/a.txt
)
a.txt
b.log

scandir sorts alphabetically and includes ./... glob supports wildcards but returns full paths. opendir streams huge directories.

The Directory class

PHP
<?php
$dir = __DIR__ . "/tmpdir";

$d = dir($dir);
echo $d->path === $dir ? "路径正确" : "路径不同", "\n";

$names = [];
while (($f = $d->read()) !== false) {
    if ($f[0] === ".") {
        continue;
    }
    $names[] = $f;
}
$d->close();

sort($names);
echo implode(",", $names), "\n";
Output
路径正确
a.txt,b.log

dir() wraps opendir in OOP style; scandir and FilesystemIterator are more common in practice.

Recursive scanning

PHP
<?php
$dir = __DIR__ . "/tmpdir";
@mkdir($dir . "/sub", 0755, true);
file_put_contents($dir . "/sub/c.txt", "c");

$files = [];
$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);

foreach ($it as $file) {
    if ($file->isFile()) {
        $files[] = $file->getFilename();
    }
}
sort($files);
echo implode(",", $files), "\n";
echo count($files), "\n";
Output
a.txt,b.log,c.txt
3

RecursiveDirectoryIterator with RecursiveIteratorIterator is the standard, and SKIP_DOTS handles the dot entries. Safer than hand-rolled recursion, which can loop on symlinks.

Create and remove

PHP
<?php
$dir = __DIR__ . "/tmpdir";

// 递归创建
mkdir($dir . "/x/y/z", 0755, true);
echo is_dir($dir . "/x/y/z") ? "已创建" : "失败", "\n";

// 递归删除
$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
    RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($it as $f) {
    $f->isDir() ? rmdir($f->getPathname()) : unlink($f->getPathname());
}
rmdir($dir);

echo is_dir($dir) ? "还在" : "已清空", "\n";
Output
已创建
已清空
Directories must be emptied before removal, hence CHILD_FIRST. The wrong order makes rmdir fail with "not empty".

Performance and safety

  • **Avoid scandir on huge dirs** —— hundreds of thousands of files eat memory
  • Watch symlink loops —— check is_link when recursing
  • Allowlist user-supplied dir names —— otherwise it is traversal
  • Double-confirm deletions —— even in code
rmdir and unlink are irreversible — no trash. Test cleanup scripts on a temp dir, or move files to a trash directory first.

Friends & Partners