原生 mkdir() 遞歸參數 (PHP 5.0+)
// 第三參數 true 開啟遞歸創建
$path = 'data/2024/03/15/logs';
if (!is_dir($path)) {
mkdir($path, 0755, true); // 權限受 umask 影響,實際為 0755 & ~umask
echo "創建成功: $path";
}
// 中文路徑需注意編碼 (Windows 文件系統通常為 GBK)
$zhPath = '數據/日誌/錯誤';
mkdir(iconv('UTF-8', 'GBK', $zhPath), 0755, true); // Linux 一般無需轉碼
自定義遞歸函數一:逐級創建 (兼容老版本、可控權限)
function mkdirs($path, $mode = 0755) {
$path = rtrim($path, '/\\');
if (is_dir($path)) return true;
$parts = explode(DIRECTORY_SEPARATOR, $path);
$current = '';
foreach ($parts as $part) {
$current .= $part . DIRECTORY_SEPARATOR;
if (!is_dir($current)) {
if (!@mkdir($current, $mode)) {
error_log("mkdirs 失敗: $current");
return false;
}
}
}
return true;
}
自定義遞歸函數二:從最深層向上檢查 (效率略高)
function mkdirsRecursive($path, $mode = 0755) {
if (is_dir($path) || @mkdir($path, $mode)) return true;
$parent = dirname($path);
if ($parent === '.' || $parent === '/') return false;
return mkdirsRecursive($parent, $mode) && @mkdir($path, $mode);
}
實戰封裝:FileSystem 工具類
class FileSystem {
public static function ensureDir($path, $mode = 0755) {
$path = rtrim($path, '/\\');
if (is_dir($path)) return true;
// Windows 路徑標準化
if (DIRECTORY_SEPARATOR === '\\') {
$path = str_replace('/', '\\', $path);
}
// 嘗試原生遞歸
if (@mkdir($path, $mode, true)) return true;
// 失敗則逐級重試 (處理權限、並發競爭)
$parts = explode(DIRECTORY_SEPARATOR, $path);
$current = '';
foreach ($parts as $part) {
$current .= $part . DIRECTORY_SEPARATOR;
if (!is_dir($current)) {
if (!@mkdir($current, $mode)) {
// 最後嘗試:可能是並發創建,再次檢查
if (!is_dir($current)) {
throw new RuntimeException("無法創建目錄: $current");
}
}
}
}
return true;
}
public static function removeDir($path) {
$path = rtrim($path, '/\\');
if (!is_dir($path)) return false;
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
}
return rmdir($path);
}
}
// 使用
FileSystem::ensureDir('storage/app/public/uploads/2024/03');
FileSystem::removeDir('storage/tmp/old');
權限最佳實踐
- 目錄 0755 (rwxr-xr-x),文件 0644 (rw-r--r--)。
- Web 服務器用戶 (www-data/nginx) 需擁有寫入權限:
chown -R www-data:www-data storage。
- 共享主機環境避免 0777,若必須則配合
.user.ini 禁用執行權限。
- Laravel/ThinkPHP 等框架已內置
Storage::makeDirectory(),優先使用框架封裝。
常見錯誤排查
| 錯誤 | 原因 | 解決 |
| Permission denied | 父目錄不可寫 | chmod/chown 父目錄,或調整 PHP-FPM 用戶組 |
| File exists | 同名文件非目錄 | unlink 後再 mkdir |
| No such file or directory | 路徑過深/編碼問題 | 逐級創建、iconv 轉碼 |
| Operation not permitted | SELinux/AppArmor 限制 | 調整安全策略或放寬上下文 |