opendir()、readdir()、closedir() 是 PHP 經典目錄操作三件套。本文從基礎用法進階到遞歸掃描、文件過濾,並介紹現代 SPL 迭代器替代方案。
. [1] => .. [2] => file1.txt [3] => dir1 ... )
$file";
}
}
closedir($dir);
?>
目錄文件列表
Files in
function scanDirRecursive($path, &$result = []) {
$handle = @opendir($path);
if (!$handle) return $result;
while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') continue;
$fullPath = $path . DIRECTORY_SEPARATOR . $file;
if (is_dir($fullPath)) {
scanDirRecursive($fullPath, $result);
} else {
$result[] = $fullPath;
}
}
closedir($handle);
return $result;
}
$allFiles = scanDirRecursive('/var/www/html');
print_r($allFiles);
PHP 5.3+ 提供 RecursiveDirectoryIterator + RecursiveIteratorIterator,代碼更簡潔、功能更強:
// 1. 遞歸遍歷所有文件(含子目錄)
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/var/www/html',
RecursiveDirectoryIterator::SKIP_DOTS) // 自動跳過 . 和 ..
);
foreach ($iterator as $file) {
if ($file->isFile()) {
echo $file->getPathname() . " ({$file->getSize()} bytes)\n";
}
}
// 2. 只獲取特定擴展名文件
$filter = new RegexIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/var/www/html', RecursiveDirectoryIterator::SKIP_DOTS)
),
'/\.php$/i'
);
foreach ($filter as $file) {
echo $file->getPathname() . "\n";
}
// 3. 限制深度
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/var/www/html', RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
$iterator->setMaxDepth(2); // 只掃描 2 層
foreach ($iterator as $file) {
echo str_repeat(' ', $iterator->getDepth()) . $file->getFilename() . "\n";
}
function getDirTree($path) {
$tree = ['name' => basename($path), 'path' => $path, 'type' => 'dir', 'children' => []];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
$pathMap = [$path => &$tree];
foreach ($iterator as $item) {
$parentPath = $item->getPath();
$currentPath = $item->getPathname();
$name = $item->getFilename();
if (!isset($pathMap[$parentPath])) continue;
$node = [
'name' => $name,
'path' => $currentPath,
'type' => $item->isDir() ? 'dir' : 'file',
'size' => $item->isFile() ? $item->getSize() : 0,
'mtime' => $item->getMTime(),
'children' => []
];
$pathMap[$parentPath]['children'][] = $node;
if ($item->isDir()) {
$pathMap[$currentPath] = &$pathMap[$parentPath]['children'][array_key_last($pathMap[$parentPath]['children'])];
}
}
return $tree;
}
$tree = getDirTree('/var/www/html');
echo json_encode($tree, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
| 方案 | 記憶體 | 速度 | 功能豐富度 | 推薦 |
|---|---|---|---|---|
| opendir + 遞歸函數 | 低(流式) | 快 | 基礎 | 舊版本兼容 |
| scandir() | 高(全載入陣列) | 快 | 基礎 | 簡單場景 |
| SPL RecursiveIterator | 低(迭代器模式) | 快 | 極高(過濾、深度、鍵值訪問) | ★★★★★ 現代首選 |
| glob() | 中 | 中 | 模式匹配 | 特定模式 |
opendir 失敗返回 false,需 @ 抑制警告並判斷。DIRECTORY_SEPARATOR 或正斜杠 /(PHP 支援)。$file->isLink() 並記錄已訪問 inode。scandir 記憶體飆升,必須用迭代器流式處理。掌握 opendir 經典三件套是基礎,但現代 PHP 專案請直接使用 SPL 迭代器,代碼更健壯、功能更強、記憶體更友好。封裝通用 DirScanner 服務類,統一項目中所有目錄操作。