PHP mkdir() 函数

定义和用法

mkdir() 函数用于创建新的目录。它可以在指定路径创建一个新的目录,并可以设置目录的权限和是否递归创建父目录。

mkdir() 函数是文件系统操作中的基础函数,常用于在文件上传、缓存文件存储、日志记录等场景中动态创建目录结构。

注意:从 PHP 5.0.0 开始,mkdir() 函数支持递归创建目录(通过 recursive 参数)。在创建目录之前,通常需要检查目录是否已存在,避免重复创建。

语法

mkdir(
    string $directory,
    int $permissions = 0777,
    bool $recursive = false,
    ?resource $context = null
): bool

参数

参数 描述
directory

必需。要创建的目录的路径。

可以是绝对路径或相对路径。相对路径将相对于当前工作目录进行解析。

permissions

可选。目录的权限,使用八进制数表示。

默认值是 0777,表示最大可能的访问权限。实际权限会受到 umask 设置的影响。

Windows 平台上此参数被忽略。

recursive

可选。是否递归创建目录。

如果设置为 true,则会创建所有必需的父目录。默认值为 false

从 PHP 5.0.0 开始支持此参数。

context

可选。流上下文(stream context)资源。

用于指定流的特殊选项,例如创建 FTP 目录时需要。

返回值

返回值 描述
TRUE 目录创建成功
FALSE 目录创建失败(权限不足、路径已存在、路径无效等)

示例

示例1:基本用法

<?php
// 创建单个目录
$dir = "/tmp/test_dir";

if (!is_dir($dir)) {
    if (mkdir($dir)) {
        echo "目录创建成功: $dir<br>";
    } else {
        echo "无法创建目录: $dir<br>";
    }
} else {
    echo "目录已存在: $dir<br>";
}

// 创建带特定权限的目录
$dir2 = "/tmp/test_dir2";
if (mkdir($dir2, 0755)) {
    echo "目录创建成功: $dir2 (权限: 0755)<br>";
}

// 清理测试目录
if (is_dir($dir)) {
    rmdir($dir);
    echo "目录已删除: $dir<br>";
}
if (is_dir($dir2)) {
    rmdir($dir2);
    echo "目录已删除: $dir2<br>";
}
?>

示例2:递归创建目录

<?php
// 递归创建多级目录
$deepDir = "/tmp/level1/level2/level3";

if (!is_dir($deepDir)) {
    // 使用 recursive 参数
    if (mkdir($deepDir, 0755, true)) {
        echo "目录创建成功: $deepDir<br>";

        // 验证各级目录
        echo "验证各级目录:<br>";
        $currentPath = "";
        $parts = explode('/', trim($deepDir, '/'));

        foreach ($parts as $part) {
            $currentPath .= '/' . $part;
            if (is_dir($currentPath)) {
                echo "- $currentPath 存在<br>";
            }
        }
    } else {
        echo "无法创建目录: $deepDir<br>";
    }
} else {
    echo "目录已存在: $deepDir<br>";
}

// 实际应用:创建上传目录结构
function createUploadDirectory($userId, $year, $month) {
    $baseDir = "/var/www/uploads";
    $userDir = $baseDir . "/user_" . $userId;
    $dateDir = $userDir . "/" . $year . "/" . str_pad($month, 2, '0', STR_PAD_LEFT);

    if (!is_dir($dateDir)) {
        if (mkdir($dateDir, 0755, true)) {
            return $dateDir;
        } else {
            return false;
        }
    }

    return $dateDir;
}

echo "<br>上传目录创建示例:<br>";
$uploadDir = createUploadDirectory(123, 2023, 12);
if ($uploadDir) {
    echo "上传目录: $uploadDir<br>";
} else {
    echo "无法创建上传目录<br>";
}
?>

示例3:处理权限和umask

<?php
// 演示权限和 umask 的影响
function createDirectoryWithPermissions($dir, $permissions = 0755) {
    // 保存当前 umask
    $oldUmask = umask(0);

    // 创建目录
    $result = mkdir($dir, $permissions, true);

    // 恢复 umask
    umask($oldUmask);

    if ($result) {
        // 获取实际权限
        clearstatcache();
        $actualPermissions = substr(sprintf('%o', fileperms($dir)), -4);

        return [
            'success' => true,
            'dir' => $dir,
            'requested_permissions' => sprintf('%04o', $permissions),
            'actual_permissions' => $actualPermissions
        ];
    }

    return ['success' => false, 'dir' => $dir];
}

// 测试不同权限设置
echo "<h4>权限设置测试</h4>";
$testDirs = [
    ['/tmp/test_0755', 0755],
    ['/tmp/test_0777', 0777],
    ['/tmp/test_0700', 0700],
    ['/tmp/test_0750', 0750]
];

foreach ($testDirs as $test) {
    list($dir, $perms) = $test;

    // 清理旧目录
    if (is_dir($dir)) {
        rmdir($dir);
    }

    $result = createDirectoryWithPermissions($dir, $perms);

    if ($result['success']) {
        echo "目录: {$result['dir']}<br>";
        echo "请求权限: {$result['requested_permissions']}<br>";
        echo "实际权限: {$result['actual_permissions']}<br><br>";

        // 清理
        rmdir($dir);
    }
}

// 实际应用:创建安全的项目目录结构
function createProjectStructure($projectName) {
    $baseDir = "/var/www/projects/" . $projectName;

    $dirs = [
        $baseDir . "/app/controllers",
        $baseDir . "/app/models",
        $baseDir . "/app/views",
        $baseDir . "/public/css",
        $baseDir . "/public/js",
        $baseDir . "/public/images",
        $baseDir . "/config",
        $baseDir . "/logs",
        $baseDir . "/cache",
        $baseDir . "/uploads"
    ];

    $permissions = [
        $baseDir => 0755,
        $baseDir . "/app" => 0755,
        $baseDir . "/app/controllers" => 0755,
        $baseDir . "/app/models" => 0755,
        $baseDir . "/app/views" => 0755,
        $baseDir . "/public" => 0755,
        $baseDir . "/public/css" => 0755,
        $baseDir . "/public/js" => 0755,
        $baseDir . "/public/images" => 0755,
        $baseDir . "/config" => 0750,  // 更严格的权限
        $baseDir . "/logs" => 0777,    // 需要写入权限
        $baseDir . "/cache" => 0777,   // 需要写入权限
        $baseDir . "/uploads" => 0777  // 需要写入权限
    ];

    foreach ($dirs as $dir) {
        if (!is_dir($dir)) {
            $perm = isset($permissions[$dir]) ? $permissions[$dir] : 0755;
            if (!mkdir($dir, $perm, true)) {
                throw new Exception("无法创建目录: $dir");
            }
            echo "创建目录: $dir (权限: " . sprintf('%04o', $perm) . ")<br>";
        }
    }

    return $baseDir;
}

try {
    echo "<br><h4>创建项目目录结构</h4>";
    $projectDir = createProjectStructure("my_project");
    echo "项目目录结构创建完成: $projectDir<br>";
} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "<br>";
}
?>

示例4:错误处理和重试机制

<?php
/**
 * 安全的目录创建函数
 */
class SafeDirectoryCreator {

    /**
     * 创建目录,带有完整的错误处理和重试机制
     */
    public static function createDirectory($path, $permissions = 0755, $maxRetries = 3) {
        // 检查目录是否已存在
        if (is_dir($path)) {
            return [
                'success' => true,
                'message' => "目录已存在: $path",
                'path' => $path
            ];
        }

        // 验证路径
        if (!self::validatePath($path)) {
            return [
                'success' => false,
                'message' => "无效的路径: $path",
                'path' => $path
            ];
        }

        // 获取父目录
        $parentDir = dirname($path);

        // 检查父目录是否存在且可写
        if (!is_dir($parentDir)) {
            $parentResult = self::createDirectory($parentDir, $permissions, $maxRetries);
            if (!$parentResult['success']) {
                return $parentResult;
            }
        } elseif (!is_writable($parentDir)) {
            return [
                'success' => false,
                'message' => "父目录不可写: $parentDir",
                'path' => $path
            ];
        }

        // 尝试创建目录(带重试)
        $attempt = 0;
        while ($attempt < $maxRetries) {
            $attempt++;

            try {
                // 设置 umask 以确保正确的权限
                $oldUmask = umask(0);
                $result = mkdir($path, $permissions);
                umask($oldUmask);

                if ($result) {
                    clearstatcache(); // 清除缓存

                    // 验证目录是否真正创建
                    if (is_dir($path)) {
                        return [
                            'success' => true,
                            'message' => "目录创建成功: $path (尝试次数: $attempt)",
                            'path' => $path,
                            'attempts' => $attempt
                        ];
                    }
                }

                // 如果失败,等待后重试
                if ($attempt < $maxRetries) {
                    usleep(100000); // 等待100毫秒
                }

            } catch (Exception $e) {
                // 记录错误但继续重试
                error_log("创建目录时出错 (尝试 $attempt): " . $e->getMessage());

                if ($attempt < $maxRetries) {
                    usleep(100000);
                }
            }
        }

        return [
            'success' => false,
            'message' => "无法创建目录: $path (最大重试次数: $maxRetries)",
            'path' => $path,
            'attempts' => $attempt
        ];
    }

    /**
     * 验证路径是否安全有效
     */
    private static function validatePath($path) {
        // 检查空路径
        if (empty($path)) {
            return false;
        }

        // 检查路径长度
        if (strlen($path) > 4096) {
            return false;
        }

        // 防止目录遍历攻击
        if (strpos($path, '..') !== false) {
            return false;
        }

        // 检查非法字符(根据操作系统)
        $invalidChars = ['<', '>', ':', '"', '|', '?', '*'];
        foreach ($invalidChars as $char) {
            if (strpos($path, $char) !== false) {
                return false;
            }
        }

        return true;
    }

    /**
     * 批量创建目录
     */
    public static function createDirectories(array $paths, $permissions = 0755) {
        $results = [];

        foreach ($paths as $index => $path) {
            $results[$index] = self::createDirectory($path, $permissions);
        }

        return $results;
    }

    /**
     * 创建临时目录
     */
    public static function createTempDirectory($prefix = 'tmp_', $permissions = 0700) {
        $tempDir = sys_get_temp_dir() . '/' . $prefix . uniqid();

        $result = self::createDirectory($tempDir, $permissions);

        if ($result['success']) {
            // 设置目录在脚本结束时自动删除
            register_shutdown_function(function() use ($tempDir) {
                if (is_dir($tempDir)) {
                    self::deleteDirectory($tempDir);
                }
            });
        }

        return $result;
    }

    /**
     * 递归删除目录
     */
    public static function deleteDirectory($dir) {
        if (!is_dir($dir)) {
            return false;
        }

        $files = array_diff(scandir($dir), ['.', '..']);

        foreach ($files as $file) {
            $path = $dir . '/' . $file;

            if (is_dir($path)) {
                self::deleteDirectory($path);
            } else {
                unlink($path);
            }
        }

        return rmdir($dir);
    }
}

// 使用示例
echo "<h4>安全目录创建示例</h4>";

// 1. 创建单个目录
$result1 = SafeDirectoryCreator::createDirectory('/tmp/safe_test/dir1');
echo "结果1: " . $result1['message'] . "<br>";

// 2. 创建已存在的目录
$result2 = SafeDirectoryCreator::createDirectory('/tmp/safe_test/dir1');
echo "结果2: " . $result2['message'] . "<br>";

// 3. 创建无效路径
$result3 = SafeDirectoryCreator::createDirectory('/tmp/../etc/passwd');
echo "结果3: " . $result3['message'] . "<br>";

// 4. 批量创建
$paths = [
    '/tmp/batch_test/dir1',
    '/tmp/batch_test/dir2/subdir',
    '/tmp/batch_test/dir3'
];

$batchResults = SafeDirectoryCreator::createDirectories($paths);
foreach ($batchResults as $index => $result) {
    echo "批量创建 {$paths[$index]}: " . $result['message'] . "<br>";
}

// 5. 创建临时目录
$tempResult = SafeDirectoryCreator::createTempDirectory('test_');
if ($tempResult['success']) {
    echo "临时目录创建: " . $tempResult['path'] . "<br>";
    // 临时目录会在脚本结束时自动删除
}
?>

示例5:使用上下文参数

<?php
// 创建FTP目录的示例
function createFtpDirectory($ftpServer, $username, $password, $remoteDir) {
    // 创建FTP连接
    $connId = ftp_connect($ftpServer);

    if (!$connId) {
        return "无法连接到FTP服务器: $ftpServer";
    }

    // 登录FTP
    if (!ftp_login($connId, $username, $password)) {
        ftp_close($connId);
        return "FTP登录失败";
    }

    // 开启被动模式
    ftp_pasv($connId, true);

    // 创建目录
    if (ftp_mkdir($connId, $remoteDir)) {
        ftp_close($connId);
        return "FTP目录创建成功: $remoteDir";
    } else {
        $error = "无法创建FTP目录: $remoteDir";
        ftp_close($connId);
        return $error;
    }
}

// 使用流上下文创建本地目录(高级用法)
function createDirectoryWithContext($dir, $contextOptions = []) {
    // 创建默认上下文选项
    $defaultOptions = [
        'notification' => function($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
            switch($notification_code) {
                case STREAM_NOTIFY_MKDIR:
                    echo "正在创建目录: $message<br>";
                    break;
                case STREAM_NOTIFY_COMPLETED:
                    echo "操作完成<br>";
                    break;
            }
        }
    ];

    // 合并选项
    $options = array_merge($defaultOptions, $contextOptions);

    // 创建流上下文
    $context = stream_context_create(['notification' => $options['notification']]);

    // 使用上下文创建目录
    return mkdir($dir, 0755, false, $context);
}

echo "<h4>使用上下文创建目录</h4>";

// 测试带通知的目录创建
$testDir = '/tmp/context_test';
if (is_dir($testDir)) {
    rmdir($testDir);
}

if (createDirectoryWithContext($testDir)) {
    echo "目录创建成功(带有上下文)<br>";
}

// 实际应用:带进度通知的批量目录创建
class DirectoryCreatorWithProgress {
    private $totalDirs = 0;
    private $createdDirs = 0;

    public function createDirectoriesWithProgress(array $dirs, $permissions = 0755) {
        $this->totalDirs = count($dirs);
        $this->createdDirs = 0;

        $results = [];

        foreach ($dirs as $index => $dir) {
            $this->createdDirs++;
            $progress = round(($this->createdDirs / $this->totalDirs) * 100, 2);

            echo "进度: $progress% - 正在创建: $dir<br>";

            if (!is_dir($dir)) {
                if (mkdir($dir, $permissions, true)) {
                    $results[$dir] = '成功';
                } else {
                    $results[$dir] = '失败';
                }
            } else {
                $results[$dir] = '已存在';
            }

            // 模拟处理延迟(实际使用时移除)
            usleep(100000);
        }

        echo "完成!共处理 {$this->totalDirs} 个目录<br>";
        return $results;
    }
}

echo "<br><h4>带进度显示的目录创建</h4>";
$creator = new DirectoryCreatorWithProgress();
$testDirs = [
    '/tmp/progress/dir1',
    '/tmp/progress/dir2/sub',
    '/tmp/progress/dir3'
];

$results = $creator->createDirectoriesWithProgress($testDirs);
echo "<br>创建结果:<br>";
print_r($results);
?>

示例6:跨平台目录创建工具

<?php
/**
 * 跨平台的目录创建工具类
 */
class CrossPlatformDirectory {

    /**
     * 创建目录(自动处理平台差异)
     */
    public static function create($path, $permissions = null, $recursive = true) {
        // 标准化路径分隔符
        $path = self::normalizePath($path);

        // 设置默认权限(根据平台)
        if ($permissions === null) {
            $permissions = self::getDefaultPermissions();
        }

        // 检查目录是否已存在
        if (is_dir($path)) {
            return true;
        }

        // 获取父目录
        $parentDir = dirname($path);

        // 如果需要递归创建,且父目录不存在
        if ($recursive && !is_dir($parentDir) && $parentDir !== $path) {
            if (!self::create($parentDir, $permissions, true)) {
                return false;
            }
        }

        // 创建目录
        $result = mkdir($path, $permissions, false);

        if ($result) {
            // 在某些平台上可能需要额外的权限设置
            self::setPlatformSpecificPermissions($path, $permissions);
        }

        return $result;
    }

    /**
     * 标准化路径(处理不同操作系统的路径分隔符)
     */
    private static function normalizePath($path) {
        // 替换Windows路径分隔符为Unix风格
        $path = str_replace('\\', '/', $path);

        // 处理多个连续的路径分隔符
        $path = preg_replace('#/+#', '/', $path);

        // 移除结尾的路径分隔符(除了根目录)
        if (strlen($path) > 1 && substr($path, -1) === '/') {
            $path = rtrim($path, '/');
        }

        return $path;
    }

    /**
     * 获取默认权限(根据平台)
     */
    private static function getDefaultPermissions() {
        if (self::isWindows()) {
            // Windows上权限设置被忽略,但这里返回一个合理的值
            return 0755;
        } else {
            // Unix-like系统使用0755
            return 0755;
        }
    }

    /**
     * 检查是否在Windows系统上运行
     */
    private static function isWindows() {
        return stripos(PHP_OS, 'WIN') === 0;
    }

    /**
     * 设置平台特定的权限
     */
    private static function setPlatformSpecificPermissions($path, $permissions) {
        if (!self::isWindows()) {
            // 在Unix-like系统上,确保权限正确设置
            chmod($path, $permissions);
        }
        // Windows上不需要额外操作
    }

    /**
     * 创建唯一的目录名
     */
    public static function createUnique($basePath, $prefix = '', $maxAttempts = 100) {
        $basePath = self::normalizePath($basePath);

        for ($i = 0; $i < $maxAttempts; $i++) {
            if ($i === 0) {
                $candidate = $basePath . '/' . $prefix;
            } else {
                $candidate = $basePath . '/' . $prefix . '_' . uniqid();
            }

            if (!is_dir($candidate)) {
                if (self::create($candidate)) {
                    return $candidate;
                }
            }
        }

        throw new Exception("无法创建唯一目录,尝试次数: $maxAttempts");
    }

    /**
     * 创建带时间戳的目录
     */
    public static function createWithTimestamp($basePath, $prefix = '') {
        $timestamp = date('Y-m-d_His');
        $dirName = $prefix . ($prefix ? '_' : '') . $timestamp;
        $fullPath = self::normalizePath($basePath . '/' . $dirName);

        if (self::create($fullPath)) {
            return $fullPath;
        }

        return false;
    }

    /**
     * 确保目录存在(如果不存在则创建)
     */
    public static function ensureExists($path, $permissions = null) {
        if (!is_dir($path)) {
            return self::create($path, $permissions, true);
        }
        return true;
    }
}

// 使用示例
echo "<h4>跨平台目录创建示例</h4>";

// 1. 基本创建
$result1 = CrossPlatformDirectory::create('/tmp/cross_test/dir1');
echo "基本创建: " . ($result1 ? '成功' : '失败') . "<br>";

// 2. 创建唯一目录
try {
    $uniqueDir = CrossPlatformDirectory::createUnique('/tmp/cross_test', 'unique');
    echo "唯一目录: $uniqueDir<br>";
} catch (Exception $e) {
    echo "唯一目录创建失败: " . $e->getMessage() . "<br>";
}

// 3. 创建带时间戳的目录
$timestampDir = CrossPlatformDirectory::createWithTimestamp('/tmp/cross_test', 'backup');
if ($timestampDir) {
    echo "时间戳目录: $timestampDir<br>";
}

// 4. 确保目录存在
$result2 = CrossPlatformDirectory::ensureExists('/tmp/cross_test/ensure_test/subdir');
echo "确保存在: " . ($result2 ? '成功' : '失败') . "<br>";

// 5. 测试Windows和Unix路径
$testPaths = [
    'C:\\Windows\\Temp\\test',  // Windows风格
    '/var/www/html/test',       // Unix风格
    'relative/path/test',       // 相对路径
    'C:/Mixed/Style/Test',      // 混合风格
];

echo "<br>路径标准化测试:<br>";
foreach ($testPaths as $path) {
    $normalized = CrossPlatformDirectory::normalizePath($path);
    echo "原始: $path -> 标准化: $normalized<br>";
}

// 实际应用:创建项目目录结构
function setupProjectDirectories($projectName, $basePath = null) {
    if ($basePath === null) {
        $basePath = CrossPlatformDirectory::isWindows() ? 'C:/projects' : '/var/www/projects';
    }

    $projectPath = CrossPlatformDirectory::normalizePath($basePath . '/' . $projectName);

    $dirs = [
        $projectPath . '/src',
        $projectPath . '/tests',
        $projectPath . '/docs',
        $projectPath . '/config',
        $projectPath . '/logs',
        $projectPath . '/cache',
        $projectPath . '/public/assets',
        $projectPath . '/vendor'
    ];

    // 确保所有目录都存在
    foreach ($dirs as $dir) {
        CrossPlatformDirectory::ensureExists($dir);
    }

    // 设置特定目录的权限(非Windows系统)
    if (!CrossPlatformDirectory::isWindows()) {
        chmod($projectPath . '/logs', 0777);
        chmod($projectPath . '/cache', 0777);
    }

    return $projectPath;
}

echo "<br><h4>项目目录设置</h4>";
$projectPath = setupProjectDirectories('my_cross_platform_project');
echo "项目目录结构创建完成: $projectPath<br>";
?>

mkdir() 常见错误代码

错误现象 可能原因 解决方法
Warning: mkdir(): Permission denied PHP进程没有在父目录中创建目录的权限 检查父目录权限,使用 is_writable() 验证
Warning: mkdir(): File exists 要创建的目录已存在 使用 is_dir() 检查目录是否存在
Warning: mkdir(): No such file or directory 父目录不存在且未使用递归模式 使用 recursive 参数或先创建父目录
目录权限与预期不符 umask 设置影响了实际权限 使用 umask(0) 临时设置 umask
在Windows上权限参数无效 Windows 忽略权限参数 使用 chmod() 在创建后设置权限(如果支持)
符号链接问题 路径包含符号链接 使用 realpath() 解析路径

最佳实践

  1. 检查目录是否存在:在创建目录前使用 is_dir() 检查,避免重复创建
  2. 使用递归参数:当需要创建多级目录时,使用 recursive = true 参数
  3. 处理权限问题:注意 umask 对权限的影响,必要时临时设置 umask(0)
  4. 错误处理:始终检查 mkdir() 的返回值,并提供有意义的错误信息
  5. 清理路径:使用 realpath() 或自定义函数规范化路径
  6. 考虑并发访问:在多进程/多线程环境中,考虑使用文件锁或唯一目录名
  7. 平台兼容性:编写跨平台代码时,注意路径分隔符和权限处理的差异
  8. 安全考虑:验证用户提供的路径,防止目录遍历攻击

性能优化技巧

批量创建优化

当需要创建多个目录时,按深度排序,先创建父目录,可以减少递归调用的开销。

缓存目录状态

如果在同一脚本中多次检查同一目录,缓存检查结果可以提高性能。

避免不必要的检查

如果确定目录不存在且父目录存在,可以直接创建,无需先检查。

使用异步操作

对于大量目录创建操作,考虑使用队列或异步任务,避免阻塞主进程。

相关函数