首页 / PHP 入门教程 / PHP 文件上传

PHP 入门教程

PHP 文件上传

本教程共 65 篇 · 第 26 篇 · 更新于 2026-07-24 · 约 5 分钟阅读

PHPPHP8文件上传$_FILESMIME安全

26. PHP 文件上传

本节目标:学会通过 HTML 表单上传文件,掌握 $_FILES 结构、上传目录配置和安全检查。

用户头像、附件、图片等资源都需要文件上传功能。本章教你安全地实现它。

26.1 上传表单的特殊配置

文件上传表单有两个必须注意的地方:

  1. method 必须是 post
  2. enctype 必须是 multipart/form-data
<!-- upload.html -->
<!DOCTYPE html>
<html>
<body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="myfile"><br>
        <input type="submit" value="上传">
    </form>
</body>
</html>
Note

忘记写 enctype 是最常见的错误。没有它,PHP 接收不到文件内容。

26.2 $_FILES 数组结构

文件上传后,PHP 将信息存入 $_FILES

<?php
print_r($_FILES["myfile"]);
/*
Array
(
    [name]     => photo.jpg        // 原始文件名
    [type]     => image/jpeg       // MIME 类型(客户端提供,不可信)
    [tmp_name] => /tmp/phpXX.tmp   // 服务器临时路径
    [error]    => 0                // 错误码,0 表示成功
    [size]     => 204800           // 文件大小(字节)
)
*/

错误码对照表

错误码含义
UPLOAD_ERR_OK (0)上传成功
UPLOAD_ERR_INI_SIZE (1)超出 upload_max_filesize 限制
UPLOAD_ERR_FORM_SIZE (2)超出表单 MAX_FILE_SIZE 限制
UPLOAD_ERR_PARTIAL (3)文件只有部分被上传
UPLOAD_ERR_NO_FILE (4)没有文件被上传
UPLOAD_ERR_NO_TMP_DIR (6)找不到临时文件夹

26.3 基本上传处理

<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $file = $_FILES["myfile"] ?? null;

    if (!$file || $file["error"] !== UPLOAD_ERR_OK) {
        die("上传失败,错误码:" . ($file["error"] ?? "无文件"));
    }

    $uploadDir = "uploads/";
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    // 为防止文件名冲突,重命名为唯一 ID
    $ext      = pathinfo($file["name"], PATHINFO_EXTENSION);
    $newName  = uniqid("img_", true) . "." . strtolower($ext);
    $destPath = $uploadDir . $newName;

    if (move_uploaded_file($file["tmp_name"], $destPath)) {
        echo "上传成功!保存为:{$newName}";
    } else {
        echo "保存文件失败";
    }
}
?>
Tip

move_uploaded_file()copy() 更安全,它会检查文件确实是通过 HTTP POST 上传的,防止路径遍历攻击。

26.4 安全检查清单

生产环境必须做以下检查:

1. 限制文件大小

<?php
$maxSize = 2 * 1024 * 1024; // 2MB
if ($file["size"] > $maxSize) {
    die("文件过大,不能超过 2MB");
}

2. 限制扩展名

<?php
$allowedExts = ["jpg", "jpeg", "png", "gif", "webp"];
$ext = strtolower(pathinfo($file["name"], PATHINFO_EXTENSION));

if (!in_array($ext, $allowedExts, true)) {
    die("只允许上传图片文件");
}
Note

仅用 $_FILES["type"] 判断不安全,它是客户端提供的。务必检查扩展名,并最好用 finfo 验证真实 MIME 类型。

3. 用 finfo 验证真实类型

<?php
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realType = $finfo->file($file["tmp_name"]);

$allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
if (!in_array($realType, $allowedTypes, true)) {
    die("文件类型不符合要求");
}

4. 防止覆盖与路径遍历

<?php
// 危险:直接使用用户提供的文件名
// move_uploaded_file($file["tmp_name"], "uploads/" . $file["name"]);

// 安全:生成新文件名
$newName = bin2hex(random_bytes(8)) . "." . $ext;
// PHP 8.0+ 可用 random_bytes,比 uniqid 更安全

26.5 多文件上传

HTML 表单加上 multiple 和数组形式的 name

<input type="file" name="photos[]" multiple>

PHP 处理时需要循环:

<?php
$files = $_FILES["photos"];
$count = count($files["name"]);

for ($i = 0; $i < $count; $i++) {
    if ($files["error"][$i] === UPLOAD_ERR_OK) {
        $ext     = pathinfo($files["name"][$i], PATHINFO_EXTENSION);
        $newName = bin2hex(random_bytes(8)) . "." . strtolower($ext);
        move_uploaded_file($files["tmp_name"][$i], "uploads/" . $newName);
        echo "已保存:{$newName}<br>";
    }
}
Tip

多文件上传时 $_FILES 结构变成二维数组,注意下标是 files["name"][0] 而非 files[0]["name"]

26.6 php.ini 相关配置

上传功能受以下配置影响:

配置项说明默认值
file_uploads是否允许上传On
upload_max_filesize单个文件最大大小2M
post_max_sizePOST 数据总大小8M
upload_tmp_dir临时文件目录系统默认
Note

post_max_size 必须大于 upload_max_filesize,否则大文件会被截断。


来源:参考了 runoob「PHP 文件上传」、w3cschool「PHP 文件上传」等,改写后所得。