基礎非遞歸版
function list_files(string $dir): void {
if (!is_dir($dir)) return;
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..' || $file === 'Thumbs.db') continue;
$path = $dir . $file;
echo '<a href="' . htmlspecialchars($path) . '" target="_blank">' . htmlspecialchars($file) . '</a><br>';
}
closedir($dh);
}
遞歸版(含子目錄)
function list_files_recursive(string $dir, int $depth = 0): void {
if (!is_dir($dir)) return;
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
if ($file === '.' || $file === '..' || $file === 'Thumbs.db') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
$indent = str_repeat(' ', $depth * 4);
if (is_dir($path)) {
echo $indent . '<strong>📁 ' . htmlspecialchars($file) . '</strong><br>';
list_files_recursive($path, $depth + 1);
} else {
echo $indent . '<a href="' . htmlspecialchars($path) . '" target="_blank">' . htmlspecialchars($file) . '</a><br>';
}
}
closedir($dh);
}
現代最佳實踐:RecursiveDirectoryIterator
function list_files_modern(string $dir): Generator {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->getFilename() === 'Thumbs.db') continue;
yield [
'path' => $file->getPathname(),
'name' => $file->getFilename(),
'isDir' => $file->isDir(),
'size' => $file->getSize(),
'mtime' => $file->getMTime(),
'depth' => $iterator->getDepth(),
];
}
}
// 使用
foreach (list_files_modern('/var/www') as $info) {
$indent = str_repeat(' ', $info['depth']);
$icon = $info['isDir'] ? '📁' : '📄';
echo "$indent$icon {$info['name']} (" . format_bytes($info['size']) . ")<br>";
}
function format_bytes(int $bytes): string {
$units = ['B','KB','MB','GB','TB'];
$i = 0;
while ($bytes >= 1024 && $i < 4) { $bytes /= 1024; $i++; }
return round($bytes, 2) . ' ' . $units[$i];
}
效能與記憶體對比
| 方法 | 記憶體 | 速度 | 功能 |
scandir() + 遞歸 | 中(全載入陣列) | 快 | 簡單 |
opendir/readdir | 低(流式) | 快 | 可控 |
RecursiveDirectoryIterator | 極低 (Generator) | 快 | 豐富資訊、可過濾、可排序 |
glob() | 高(全路徑陣列) | 慢 | 模式匹配 |
安全提醒
- 禁止直接暴露服務器絕對路徑給前端;提供下載請用 PHP 代理輸出。
- 過濾
.. 防止目錄遍歷攻擊。
- 大目錄建議分頁或 AJAX 懶加載,避免單次響應過大。