PHP 文件上传(附 3 个核心步骤),新手避坑一次讲清

编程狮(w3cschool.cn) 2026-09-29 12:10:24 浏览数 (19)
反馈

PHP 文件上传怎么跑通又安全?

你在做用户头像、附件提交时,是不是卡在“前端选了文件,后端却收不到”或者“上传成功但打不开”?PHP 文件上传,核心就三步:前端用带 enctype 的表单、后端用 $_FILES 接收、再用 move_uploaded_file 落盘。本文用最小示例讲清每一步、怎么校验文件类型,以及三个最容易踩的坑。看完你能跑通一个安全可用的上传接口。今天这篇文章,编程狮就把这块讲透。不管是做头像上传、附件提交还是后台导入,照着下面的步骤和校验清单走,基本能避开九成以上的上传坑。

一、先看结论

步骤 关键动作 易错点
前端表单 method="post" + enctype="multipart/form-data" 漏写 enctype 会导致 $_FILES 为空
后端接收 读 $_FILES['file'] 的临时路径与错误码 不判断 error 直接搬文件
落盘保存 move_uploaded_file(临时路径, 目标路径) 不校验类型/大小,留安全后门

PHP 文件上传:你卡在哪一步?

二、上传到底经过了什么

PHP 文件上传 的本质:浏览器把文件打成 multipart 格式发给服务器,PHP 先把文件存到一个“邪修 PHP”,你在脚本里看到的 $_FILES['file']['tmp_name'] 就是那个临时文件。你必须在一个请求生命周期内,用 move_uploaded_file 把它搬到你想留的地方,否则请求结束临时文件就被删了。

一个比喻:临时目录像快递柜,快递员(PHP)把包裹放进去,你(你的代码)得在柜子清空前把包裹搬回家(目标目录),否则就丢件。建议先过一遍 PHP 教程 的表单章节,理解 POST 和文件域。

三、步骤一:前端表单怎么写

<!-- 上传表单必须带 enctype,否则文件传不过来 -->
<form method="post" enctype="multipart/form-data" action="upload.php">
    <!-- name="file" 对应后端 $_FILES['file'] -->
    <input type="file" name="file">
    <button type="submit">上传</button>
</form>

上面这段做了什么:<form> 里 method="post" 保证大文件能传,enctype="multipart/form-data" 告诉浏览器“这次要把文件内容一起编码发送”。input 的 name="file" 决定了后端用 $_FILES['file'] 来取。漏掉 enctype 是最常见的“收不到文件”原因。

四、步骤二与三:后端接收并落盘

<?php
// 演示:接收名为 file 的上传,并搬到 uploads 目录
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1) 先判断有没有错误码
    if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
        exit('上传出错,错误码:' . $_FILES['file']['error']);
    }
    // 2) 取临时路径(请求结束前有效)
    $tmp  = $_FILES['file']['tmp_name'];
    $name = basename($_FILES['file']['name']);   // 防目录穿越
    $dest = __DIR__ . '/uploads/' . $name;
    // 3) 搬到目标位置
    if (move_uploaded_file($tmp, $dest)) {
        echo '上传成功:' . $dest;
    } else {
        echo '落盘失败';
    }
}
?>

上面这段做了什么:先用 error 字段确认上传本身没问题;basename 去掉用户传来的路径,防止“目录穿越”攻击;最后用 move_uploaded_file 把临时文件搬到 uploads/ 下。注意目标目录要提前建好且有写权限。

五、三个最容易踩的坑

现象 原因 修复
$_FILES 为空 表单漏写 enctype="multipart/form-data" 补上 enctype
大文件上传被截断 php.ini 的 upload_max_filesize / post_max_size 太小 调大这两个值或前端分片
任意文件被执行成木马 不校验后缀/类型,允许用户传 .php 白名单校验后缀,上传目录禁执行

第一个坑前面说过,是新手第一道坎。第二个坑:即使代码没错,php.ini 默认只允许 2M,传图片没事,传视频就失败,要看错误码 UPLOAD_ERR_INI_SIZE。第三个坑最危险:如果上传目录能被当脚本执行,攻击者传个 .php 就能拿到服务器权限。务必用后缀白名单(只允许图片等),并把上传目录配置成不可执行。

真正把文件上传跑通,先要理解 PHP 收到的数据长什么样。前端用 multipart/form-data 提交后,PHP 会把文件信息放到 $_FILES 超全局数组里,里面包含 name(原始文件名)、type(浏览器声明的 MIME)、tmp_name(服务器上的临时路径)、error(错误码)和 size(字节数)。关键点在于:文件在请求结束后会被自动删除,所以你必须用 move_uploaded_file($tmp_name, $dest) 把它搬到目标目录,且这个函数会顺手校验文件确实是通过 HTTP 上传的,比直接 copy 安全得多;配套的 is_uploaded_file($tmp_name) 也能先做一道检查。

配置层面有三个地方最容易卡住新手。其一是 php.ini 的 upload_max_filesize,它限制单个文件大小;其二是 post_max_size,它限制整个请求体的大小,必须比前者大;其三是表单里要写 enctype="multipart/form-data",否则 $_FILES 根本收不到东西。改完配置记得重启服务,很多“上传后 $_FILES 为空”的 bug 都是这三个没对齐。

安全校验比功能更值得花篇幅。永远不要相信浏览器传来的 type 和 name:前者能伪造,后者常带路径(比如 ../../evil.php),直接拿来拼路径会造成目录穿越甚至上传恶意脚本。正确做法是:第一,用 finfo 或 getimagesize 按文件真实内容判断类型,而不是看后缀;第二,服务端用白名单限制允许的类型和大小;第三,生成新的随机文件名(比如 uniqid() 加哈希),彻底丢弃用户给的名字;第四,把上传目录放在 Web 根目录之外,或配置服务器禁止该目录执行脚本,防止上传的 php 文件被直接访问执行。把这套清单做成上传前的检查函数,基本能挡掉绝大多数上传漏洞。

还有一个实用技巧:上传成功后最好把文件信息(原文件名、大小、存储路径、上传时间)写进数据库,方便后续做“我的文件”列表和定期清理。前端可以用进度条配合 XMLHttpRequest 的 upload.onprogress 展示实时进度,体验更好。牢记“先校验后落盘、随机命名、目录禁执行”这三句话,上传功能就既好用又安全。

部署前先配置好 PHP 与 Web 服务器,把示例表单和后端脚本运行起来,检查上传结果是否符合预期;若 $_FILES 为空,按“漏写 enctype”的方向排查失败原因并修复。安全一项根据官方文档整理,未在本机逐环境执行。

总结

要点带走:

  • 5 大语言对比必须写 enctype="multipart/form-data",否则 $_FILES 收不到;
  • 后端先判 error 再搬文件,用 basename 防目录穿越;
  • 用 move_uploaded_file 在请求结束前落盘,目标目录需可写;
  • 安全底线:后缀白名单 + 上传目录禁执行,别让任何人传可执行文件。

下一步建议系统过一遍 PHP 教程,把表单、文件系统和安全章节一起看。

延伸学习

想把这块知识系统补齐,可以按这个顺序来:

  1. PHP 速查手册
  2. 在线工具推荐
  3. C 与 PHP 对比

    常见问题

Q:为什么我后端 $_FILES 是空的?

A:十有八九是前端 form 漏写了 enctype="multipart/form-data"。没有它,浏览器只发送表单字段名,不会把文件内容编码进去,后端自然收不到。

Q:上传大文件失败但代码没错,怎么办?

A:看 $_FILES['file']['error'] 的错误码。若是 UPLOAD_ERR_INI_SIZE,说明超过 php.ini 的 upload_max_filesize 或 post_max_size,需要调大这两个配置,或改用前端分片上传。

Q:上传目录为什么要禁执行?

A:如果上传目录里的文件能被当 PHP 执行,攻击者传一个 .php 木马就能控制服务器。用后缀白名单只允许图片等安全类型,并在服务器配置里禁止该目录执行脚本,是基本的安全底线。

0 人点赞