mkdir('/a/b/c') 若 /a/b 不存在,**直接返回 false** 並拋出 Warning。這是新手最常踩的坑。
<?php
// PHP 5.0.0+ 支援第三參數 $recursive
mkdir('/a/b/c', 0755, true); // true = 遞歸創建所有不存在的父目錄
?>
0755:權限掩碼(八進制),建議目錄用 0755,文件用 0644。true:遞歸模式,**務必加上**。原文提供的 fileDir() 寫法較繁瑣,現代 PHP 推薦:
<?php
// 當前腳本目錄(絕對路徑,無尾部斜槓)
$scriptDir = __DIR__; // PHP 5.3+ 魔術常數,最推薦
// 或
$scriptDir = dirname(__FILE__); // 兼容舊版本
// 當前工作目錄(可能非腳本目錄,視執行方式而定)
$cwd = getcwd();
// 組合目標路徑
$targetDir = $scriptDir . '/uploads/images/2024/';
mkdir($targetDir, 0755, true);
?>
<?php
/**
* 安全創建目錄(遞歸、權限、異常處理、競爭條件緩解)
* @param string $path 目標目錄絕對路徑
* @param int $mode 權限掩碼,預設 0755
* @return bool 是否創建成功或已存在
* @throws RuntimeException 權限不足或路徑非法
*/
function ensure_dir(string $path, int $mode = 0755): bool {
// 1. 規範化路徑
$path = rtrim($path, '/\\');
if ($path === '') throw new InvalidArgumentException('路徑不能為空');
// 2. 已存在且是目錄 → 直接成功
if (is_dir($path)) return true;
// 3. 存在但非目錄(同名文件/連結)→ 拋錯
if (file_exists($path)) {
throw new RuntimeException("路徑已存在但非目錄:{$path}");
}
// 4. 遞歸創建(使用 @ 抑制 warning,自行判斷返回值)
$result = @mkdir($path, $mode, true);
if ($result === false) {
// 可能是併發下其他進程剛創建好,再次檢查
if (is_dir($path)) return true;
$error = error_get_last();
throw new RuntimeException("創建目錄失敗:{$path} - " . ($error['message'] ?? '未知錯誤'));
}
// 5. 可選:修正權限(受 umask 影響)
@chmod($path, $mode);
return true;
}
// 使用
try {
ensure_dir(__DIR__ . '/storage/logs');
ensure_dir('/var/www/html/uploads/avatar');
} catch (Throwable $e) {
error_log($e->getMessage());
}
?>
模式 0755 & umask 0022 = 實際 0755 (rwxr-xr-x) 模式 0777 & umask 0022 = 實際 0755 (umask 預設會拿走寫權限) 建議:顯式 chmod 或設置 umask(0) 後創建(需謹慎,影響全局)
| 場景 | 對策 |
|---|---|
| 上傳目錄按日期分層 | ensure_dir("uploads/" . date('Y/m/d')); |
| 緩存目錄需 Web 寫入、CLI 讀取 | 設置 0775 + 同組用戶;或 ACL setfacl -m g:www-data:rwx |
| Windows 開發、Linux 生產 | 路徑分隔符用 DIRECTORY_SEPARATOR 或統一正斜槓 /(PHP 自動兼容) |
| Nginx + PHP-FPM 權限不一致 | 確保目錄擁有者為 www-data 或 PHP-FPM 運行用戶,組權限 rwx |
mkdir($path, 0755, true) 遞歸創建。__DIR__ 而非 getcwd()。ensure_dir() 統一處理異常、權限、併發。掌握這幾點,徹底告別「mkdir 失敗」煩惱。