目錄操作是檔案系統管理的基石。本文提供四個獨立、可重複使用的函式,分別實現:遞迴列出所有檔案、遞迴複製整個目錄樹、遞迴刪除目錄、遞迴計算目錄總大小。每個函式均採用「深度優先遞迴」模式,程式碼簡潔並附詳細註解。
listDir()<?php
/**
* 遞迴列出目錄下所有檔案(不含目錄本身)
* @param string $dir 目錄路徑
* @param array $result 參考傳遞收集結果
* @return array 檔案相對路徑陣列
*/
function listDir(string $dir, array &$result = []): array {
$ds = opendir($dir);
while (($file = readdir($ds)) !== false) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
listDir($path, $result); // 遞迴進入子目錄
} else {
// 可選:回傳相對路徑而非絕對路徑
$result[] = substr($path, strlen($dir) + 1);
}
}
closedir($ds);
return $result;
}
// 使用
$files = listDir(__DIR__ . '/storage');
print_r($files);
/* 輸出範例:
Array
(
[0] => logs/app.log
[1] => uploads/image.png
[2] => config/database.php
)
*/
copyDir()<?php
/**
* 遞迴複製整個目錄樹
* @param string $src 來源目錄
* @param string $dst 目標目錄(不存在會自動建立)
* @param bool $overwrite 是否覆蓋同名檔案
* @return bool
*/
function copyDir(string $src, string $dst, bool $overwrite = true): bool {
if (!is_dir($src)) return false;
if (!is_dir($dst)) {
if (!mkdir($dst, 0755, true)) return false; // 遞迴建立目標目錄
}
$ds = opendir($src);
while (($file = readdir($ds)) !== false) {
if ($file === '.' || $file === '..') continue;
$srcPath = $src . DIRECTORY_SEPARATOR . $file;
$dstPath = $dst . DIRECTORY_SEPARATOR . $file;
if (is_dir($srcPath)) {
if (!copyDir($srcPath, $dstPath, $overwrite)) return false;
} else {
if ($overwrite || !file_exists($dstPath)) {
if (!copy($srcPath, $dstPath)) return false;
// 保留檔案權限與修改時間
$stat = stat($srcPath);
chmod($dstPath, $stat['mode'] & 0777);
touch($dstPath, $stat['mtime']);
}
}
}
closedir($ds);
return true;
}
// 使用
copyDir(__DIR__ . '/source', __DIR__ . '/backup_' . date('Ymd'));
deleteDir()<?php
/**
* 遞迴刪除目錄及其所有內容
* @param string $dir 目錄路徑
* @return bool
*/
function deleteDir(string $dir): bool {
if (!is_dir($dir)) return false;
$ds = opendir($dir);
while (($file = readdir($ds)) !== false) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
deleteDir($path); // 先刪除子目錄
} else {
unlink($path); // 再刪除檔案
}
}
closedir($ds);
return rmdir($dir); // 最後刪除空目錄本身
}
// 使用
deleteDir(__DIR__ . '/temp_cache');
dirSize()<?php
/**
* 遞迴計算目錄總大小(位元組)
* @param string $dir 目錄路徑
* @return int 總位元組數
*/
function dirSize(string $dir): int {
$total = 0;
$ds = opendir($dir);
while (($file = readdir($ds)) !== false) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
$total += dirSize($path); // 累加子目錄大小
} else {
$total += filesize($path); // 累加檔案大小
}
}
closedir($ds);
return $total;
}
// 使用
$bytes = dirSize(__DIR__ . '/uploads');
echo "總大小:" . formatBytes($bytes); // 需配合上一篇的 formatBytes()
function formatBytes(int $bytes, int $precision = 2): string {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$pow = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
$pow = min($pow, count($units) - 1);
return round($bytes / (1024 ** $pow), $precision) . ' ' . $units[$pow];
}
| 特性 | 深度優先(本文採用) | 廣度優先(佇列實作) |
|---|---|---|
| 程式碼複雜度 | 低(自然遞迴) | 中(需顯式佇列/堆疊) |
| 記憶體使用 | 呼叫堆疊深度 = 樹高 | 佇列大寬度 = 最大層級節點數 |
| 適用場景 | 目錄樹通常較深不寬 | 極寬目錄結構(如百萬檔案單層) |
| 中斷恢復 | 困難 | 容易(可序列化佇列) |
PHP 5.3+ 內建 RecursiveDirectoryIterator + RecursiveIteratorIterator,可用更優雅的方式實現相同功能:
// 列出所有檔案
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) echo $file->getPathname() . "\n";
}
// 計算大小
$size = 0;
foreach ($iterator as $file) {
if ($file->isFile()) $size += $file->getSize();
}
優點:自動處理遞迴、支援過濾器、可直接搭配 RegexIterator 過濾副檔名。
缺點:對符號連結、權限錯誤的處理需額外 try-catch,效能略低於原生遞迴函式。
掌握「開啟目錄 → 讀取項目 → 判斷類型 → 遞迴或處理 → 關閉目錄」這個經典遞迴模板,即可輕鬆應對列表、複製、刪除、計算大小等所有目錄操作需求。建議將這四個函式封裝為 Filesystem 服務類別,並加入 例外處理、進度回呼、乾跑模式,即可滿足企業級部署腳本、備份工具、同步程式的開發需求。