PHP 內建 readdir() 僅能讀取單層目錄。本文提供兩個實用類別:DirLister 遞歸列出所有檔案與子目錄,DirRemover 安全刪除非空資料夾,並包含錯誤處理與輸出格式化。
class DirLister {
private $root_dir;
private $depth = 0;
private $file_list = [];
private $output_html = '';
public function __construct($dir) {
$this->root_dir = rtrim($dir, '/\\');
}
// 回傳多維陣列:[深度][檔案名...] + 'directory' => 目錄路徑
public function getList($dir = '') {
$dir = $dir ?: $this->root_dir;
$handle = @opendir($dir);
if (!$handle) {
throw new RuntimeException("無法開啟目錄: $dir");
}
while (false !== ($item = readdir($handle))) {
if ($item === '.' || $item === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$this->depth++;
$this->getList($path);
} else {
$this->file_list[$this->depth][] = $item;
}
}
$this->file_list[$this->depth]['directory'] = $dir;
$this->depth--;
closedir($handle);
return $this->file_list;
}
// 回傳 HTML 巢狀列表 (- ...)
public function toHtml($dir = '') {
$dir = $dir ?: $this->root_dir;
$handle = @opendir($dir);
if (!$handle) return '';
$this->output_html .= "
\n";
$this->output_html .= "- Directory: " . htmlspecialchars($dir) . "
\n";
while (false !== ($item = readdir($handle))) {
if ($item === '.' || $item === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$this->depth++;
$this->toHtml($path);
} else {
$this->output_html .= "- " . htmlspecialchars($item) . "
\n";
}
}
$this->depth--;
closedir($handle);
$this->output_html .= "
\n";
return $this->output_html;
}
}
// 使用範例
$lister = new DirLister('jquery');
echo $lister->toHtml();
// 或取得陣列: print_r($lister->getList());
class DirRemover {
public static function delete($dir) {
$dir = rtrim($dir, '/\\');
if (!is_dir($dir)) {
throw new InvalidArgumentException("目錄不存在: $dir");
}
$handle = opendir($dir);
while (false !== ($file = readdir($handle))) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
self::delete($path); // 遞歸刪除子目錄
} else {
if (@unlink($path)) {
echo "檔案刪除成功: $path
\n";
} else {
echo "檔案刪除失敗: $path
\n";
}
}
}
closedir($handle);
if (@rmdir($dir)) {
echo "目錄刪除成功: $dir
\n";
return true;
} else {
echo "目錄刪除失敗: $dir
\n";
return false;
}
}
}
// 使用範例
try {
DirRemover::delete('/var/www/test');
} catch (Exception $e) {
echo "錯誤: " . $e->getMessage();
}
PHP 5.3+ 內建 RecursiveDirectoryIterator + RecursiveIteratorIterator 更簡潔高效:
// 列出所有檔案 (含路徑)
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/path/to/dir', RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($files as $file) {
echo $file->isDir() ? "[DIR] " : "[FILE] ";
echo $file->getPathname() . "\n";
}
// 刪除非空目錄 (需由最深層開始刪除,故用 CHILD_FIRST)
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/path/to/dir', RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
}
rmdir('/path/to/dir'); // 最後刪除根目錄
| 方法 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 自訂遞歸函式 | 邏輯可控、易加入業務邏輯 | 深層目錄可能超出記憶體/堆疊限制 | 需自訂輸出格式、權限檢查 |
| SPL 迭代器 | 內建優化、記憶體效率佳、程式碼極簡 | 較難插入自訂邏輯、錯誤處理需額外 try-catch | 標準列出/刪除操作 |
| 系統指令 (exec rm -rf) | 速度最快、處理特殊權限 | 安全風險極高、跨平台相容性差 | 極大量檔案、容器環境、已驗證路徑安全 |
realpath() 確認路徑在允許範圍內,防止目錄遍歷攻擊usleep() 避免阻塞