文件上传失败且$_files为空,主因是表单缺少enctype="multipart/form-data"或php.ini中file_uploads=off;需同步检查post_max_size、upload_max_filesize及nginx的client_max_body_size。

文件上传失败但 $_FILES 为空?检查 form enctype 和 PHP 配置
最常见的现象是 $_FILES 数组完全为空,连键名都不出现。这不是代码写错了,而是表单或服务端拦下了请求。
- 确保 HTML 表单用了
enctype="multipart/form-data"—— 少了这个,浏览器根本不会把文件内容发出去 - 检查
php.ini中的file_uploads = On,有些 Docker 镜像或轻量环境默认关掉它 -
post_max_size和upload_max_filesize必须都大于你要传的文件;注意单位(2M≠2048K,PHP 解析时对空格和大小写敏感) - 如果用 Nginx,还要确认
client_max_body_size没卡住请求,否则 413 错误根本到不了 PHP 层
move_uploaded_file() 总返回 false?别跳过错误检查
很多人直接写 move_uploaded_file($_FILES['file']['tmp_name'], $dest),结果失败也不报错,文件就丢了。
- 必须先检查
$_FILES['file']['error'] === UPLOAD_ERR_OK—— 其他错误码(比如UPLOAD_ERR_FORM_SIZE或UPLOAD_ERR_NO_FILE)会导致tmp_name无效 -
move_uploaded_file()只接受tmp_name路径,不能传普通变量或拼接字符串;路径里含中文或特殊字符时,确保目标目录有写权限且编码一致(Linux 下常见 utf-8 vs locale 不匹配) - 目标目录必须存在且可写,PHP 不会自动创建父级目录;用
is_dir()+mkdir(..., 0755, true)安全补全
怎么安全保存用户上传的文件名?别信 $_FILES['name']
前端传来的 $_FILES['file']['name'] 是纯客户端输入,可能带路径、空字节、危险扩展名,甚至绕过 JS 校验。
- 绝对不要直接拼进
move_uploaded_file()的第二参数;用pathinfo($name, PATHINFO_FILENAME)和pathinfo($name, PATHINFO_EXTENSION)拆开处理 - 扩展名要白名单校验(比如只允许
['jpg', 'png', 'pdf']),别用黑名单;再用finfo_open(FILEINFO_MIME_TYPE)检查真实 MIME 类型,防止伪造 - 生成新文件名:推荐用
uniqid() . '_' . random_int(1000, 9999),避免时间戳冲突;存储时记录原始名到数据库,别改原始语义
大文件上传中断或超时?得配合前端分片或后端流式处理
单纯调大 upload_max_filesize 和 max_execution_time 只治标。用户网络差、PHP 超时、Nginx 读取超时三者叠加,上传很容易静默失败。
立即学习“PHP免费学习笔记(深入)”;
- 单次上传建议控制在 20MB 以内;更大文件必须上分片(如使用
web-uploader或Uppy),后端按块接收、合并、校验 - PHP 层若需流式处理(比如边传边转码),要用
fopen('php://input', 'r')配合$_SERVER['CONTENT_LENGTH'],但此时$_FILES不可用,得自己解析 multipart boundary - 上传中刷新页面或关闭标签页,PHP 的
$_FILES临时文件仍会残留;加个清理脚本定期扫sys_get_temp_dir()下过期的php*文件
上传逻辑看着简单,真正上线时最麻烦的永远不是怎么写,而是各种边界情况下的静默失败——比如用户选了 100MB 文件却没看到提示,或者同一文件名被并发上传覆盖。留好日志,每个 move_uploaded_file() 后都记下 $_FILES['file']['error'] 和实际移动路径,出问题才不抓瞎。











