在檔案上傳、快取目錄初始化、日誌分級儲存等場景中,我們常需要一次建立多層巢狀目錄(例如 uploads/2024/05/15/)。PHP 內建的 mkdir() 在 PHP 5.0 之後支援 recursive 參數,但理解其底層遞迴原理,有助於在舊版本環境或特殊權限情境下靈活處理。
最簡單的做法是直接啟用第三個參數:
<?php
$path = 'storage/logs/2024/05/15';
if (!is_dir($path)) {
mkdir($path, 0775, true); // true 表示遞迴建立
}
?>
此方法會自動建立所有不存在的父目錄,並套用指定權限(受 umask 影響)。
若環境限制無法使用遞迴參數,或需自訂建立邏輯(如逐層檢查權限、記錄建立日誌),可參考以下遞迴函式:
<?php
/**
* 遞迴建立目錄
* @param string $dir 目標路徑
* @param int $mode 權限模式(預設 0777)
* @return bool 是否成功
*/
function mk_dir($dir, $mode = 0777) {
// 終止條件:目錄已存在或為根目錄
if (is_dir($dir) || $dir === '/' || $dir === '.') {
return true;
}
// 遞迴建立父目錄
$parent = dirname($dir);
if (!mk_dir($parent, $mode)) {
return false; // 父目錄建立失敗,直接返回
}
// 建立當前目錄
return @mkdir($dir, $mode);
}
?>
@mkdir 避免因競態條件(並發建立同一目錄)拋出警告,實際專案建議改用 file_exists 二次確認或捕獲例外。$mode,實際生效權限為 $mode & ~umask()。| 問題 | 原因 | 對策 |
|---|---|---|
| Permission denied | 執行腳本的使用者無寫入權限 | 調整父目錄擁有者/群組,或使用 ACL 設定細粒度權限 |
| 遞迴過深導致堆疊溢位 | 極長路徑(超過系統限制) | 改用迴圈迭代實作,或縮短目錄層級 |
| Windows 路徑分隔符不一致 | 混用 / 與 \ | 統一使用 DIRECTORY_SEPARATOR 或 /(PHP 自動轉換) |
在高併發系統中,建議採用「雙重檢查鎖定」模式:
<?php
function ensure_dir($path) {
if (is_dir($path)) return true; // 快速路徑
$lock = fopen($path . '.lock', 'w');
if (flock($lock, LOCK_EX)) {
if (!is_dir($path)) mkdir($path, 0775, true);
flock($lock, LOCK_UN);
}
fclose($lock);
return is_dir($path);
}
?>
此模式可防止多行程同時建立同一目錄造成資源浪費或錯誤日誌噪音。
掌握目錄遞迴建立的原理與變體,能讓你在檔案系統操作中更從容應對各種邊界情況。