PHP move_uploaded_file() 函数

提示:move_uploaded_file() 函数用于将上传的文件移动到新位置。此函数检查并确保文件是通过 HTTP POST 上传的合法文件,是处理文件上传的标准方法。

定义和用法

move_uploaded_file() 函数用于将上传的文件移动到新位置。此函数是 PHP 中处理文件上传的标准方法。

函数会检查目标文件是否是通过 HTTP POST 上传的,如果是,则将其移动到指定位置。如果不是通过 POST 上传的文件,函数会返回 false。

语法

move_uploaded_file ( string $filename , string $destination ) : bool

参数

参数 类型 说明
filename 字符串 上传文件的临时文件名(来自 $_FILES['userfile']['tmp_name'])
destination 字符串 文件移动的目标路径,包括新文件名

返回值

成功时返回 true,失败时返回 false

示例

示例 1:基本文件上传

HTML 表单代码:

<!DOCTYPE html>
<html>
<head>
    <title>文件上传示例</title>
</head>
<body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        <label for="file">选择要上传的文件:</label>
        <input type="file" name="userfile" id="file">
        <input type="submit" value="上传">
    </form>
</body>
</html>

PHP 处理代码(upload.php):

<?php
// 确保是 POST 请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 检查文件上传是否成功
    if (isset($_FILES['userfile']) && $_FILES['userfile']['error'] === UPLOAD_ERR_OK) {
        $uploadDir = 'uploads/';

        // 确保上传目录存在
        if (!is_dir($uploadDir)) {
            mkdir($uploadDir, 0755, true);
        }

        // 生成安全的文件名
        $originalName = basename($_FILES['userfile']['name']);
        $extension = pathinfo($originalName, PATHINFO_EXTENSION);
        $safeName = uniqid('file_') . '.' . $extension;
        $destination = $uploadDir . $safeName;

        // 移动上传的文件
        if (move_uploaded_file($_FILES['userfile']['tmp_name'], $destination)) {
            echo "文件上传成功!<br>";
            echo "原始文件名: " . htmlspecialchars($originalName) . "<br>";
            echo "保存路径: " . htmlspecialchars($destination) . "<br>";
            echo "文件大小: " . $_FILES['userfile']['size'] . " 字节";
        } else {
            echo "文件移动失败。";
        }
    } else {
        echo "文件上传失败。错误代码: " . $_FILES['userfile']['error'];
    }
}
?>

示例 2:多文件上传

HTML 表单代码:

<!DOCTYPE html>
<html>
<head>
    <title>多文件上传</title>
</head>
<body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        <label for="files">选择多个文件:</label>
        <input type="file" name="userfiles[]" id="files" multiple>
        <input type="submit" value="上传">
    </form>
</body>
</html>

PHP 处理代码:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $uploadDir = 'uploads/';
    $results = [];

    // 确保上传目录存在
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    // 处理每个上传的文件
    foreach ($_FILES['userfiles']['error'] as $key => $error) {
        if ($error === UPLOAD_ERR_OK) {
            $originalName = basename($_FILES['userfiles']['name'][$key]);
            $tmpName = $_FILES['userfiles']['tmp_name'][$key];
            $fileSize = $_FILES['userfiles']['size'][$key];

            // 生成安全的文件名
            $extension = pathinfo($originalName, PATHINFO_EXTENSION);
            $safeName = uniqid('file_') . '.' . $extension;
            $destination = $uploadDir . $safeName;

            // 移动文件
            if (move_uploaded_file($tmpName, $destination)) {
                $results[] = [
                    'original' => $originalName,
                    'saved' => $destination,
                    'size' => $fileSize,
                    'success' => true
                ];
            } else {
                $results[] = [
                    'original' => $originalName,
                    'success' => false
                ];
            }
        }
    }

    // 显示结果
    echo "上传结果:<br><br>";
    foreach ($results as $result) {
        if ($result['success']) {
            echo "✓ " . htmlspecialchars($result['original']) . " 上传成功 (" . $result['size'] . " 字节)<br>";
        } else {
            echo "✗ " . htmlspecialchars($result['original']) . " 上传失败<br>";
        }
    }
}
?>

示例 3:文件类型和大小验证

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $uploadDir = 'uploads/';
    $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
    $maxFileSize = 2 * 1024 * 1024; // 2MB

    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    if (isset($_FILES['userfile']) && $_FILES['userfile']['error'] === UPLOAD_ERR_OK) {
        $tmpName = $_FILES['userfile']['tmp_name'];
        $originalName = $_FILES['userfile']['name'];
        $fileSize = $_FILES['userfile']['size'];
        $fileType = $_FILES['userfile']['type'];

        // 验证文件类型
        if (!in_array($fileType, $allowedTypes)) {
            die("错误:不允许的文件类型。只允许:" . implode(', ', $allowedTypes));
        }

        // 验证文件大小
        if ($fileSize > $maxFileSize) {
            die("错误:文件太大。最大允许 " . ($maxFileSize / 1024 / 1024) . "MB");
        }

        // 进一步验证文件类型(通过文件内容)
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mimeType = finfo_file($finfo, $tmpName);
        finfo_close($finfo);

        if (!in_array($mimeType, $allowedTypes)) {
            die("错误:文件类型验证失败");
        }

        // 生成安全的文件名
        $extension = pathinfo($originalName, PATHINFO_EXTENSION);
        $safeName = md5_file($tmpName) . '.' . $extension;
        $destination = $uploadDir . $safeName;

        // 移动文件
        if (move_uploaded_file($tmpName, $destination)) {
            echo "文件验证成功并已保存!";
        } else {
            echo "文件保存失败";
        }
    } else {
        echo "文件上传失败";
    }
}
?>

$_FILES 数组结构

当文件上传时,PHP 会将文件信息存储在 $_FILES 超级全局数组中。对于名为 "userfile" 的文件输入字段,$_FILES['userfile'] 包含:

键名 说明
name 原始文件名
type 文件的 MIME 类型(由浏览器提供,不可靠)
tmp_name 文件的临时存储路径(用于 move_uploaded_file() 的第一个参数)
error 错误代码(UPLOAD_ERR_OK 表示成功)
size 文件大小(字节)

文件上传错误代码

错误代码 常量 说明
0 UPLOAD_ERR_OK 文件上传成功
1 UPLOAD_ERR_INI_SIZE 文件大小超过 php.ini 中的 upload_max_filesize 限制
2 UPLOAD_ERR_FORM_SIZE 文件大小超过表单中的 MAX_FILE_SIZE 限制
3 UPLOAD_ERR_PARTIAL 文件只有部分被上传
4 UPLOAD_ERR_NO_FILE 没有文件被上传
6 UPLOAD_ERR_NO_TMP_DIR 找不到临时文件夹
7 UPLOAD_ERR_CANT_WRITE 文件写入失败
8 UPLOAD_ERR_EXTENSION PHP 扩展停止了文件上传

注意事项

  • 安全检查:move_uploaded_file() 会自动检查文件是否通过 HTTP POST 上传,防止文件上传攻击
  • 权限设置:确保目标目录有适当的写入权限(通常为 755 或 775)
  • 文件覆盖:如果目标文件已存在,move_uploaded_file() 会覆盖它(PHP 8.0.0+ 会发出警告)
  • 临时文件:上传的文件存储在临时目录中,脚本执行结束后会自动删除
  • MIME 类型验证:不要依赖 $_FILES['type'] 验证文件类型,因为它可能被伪造。使用 finfo_file() 进行验证
  • 文件名安全:对上传的文件名进行清理,防止路径遍历攻击
  • 大小限制:确保 php.ini 中的 upload_max_filesize 和 post_max_size 设置足够大

最佳实践

  1. 始终验证文件类型(使用文件内容验证,而非扩展名)
  2. 限制文件大小(服务器端和客户端都要验证)
  3. 为上传的文件生成唯一文件名,防止文件名冲突
  4. 将上传的文件存储在 Web 根目录之外,或配置适当的访问权限
  5. 对图片文件,使用 getimagesize() 验证确实是有效的图片
  6. 设置适当的 HTTP 头部,防止文件在浏览器中被执行

相关函数