PHP本身不处理上传进度,需前端轮询服务端存储的进度信息;必须启用session.upload_progress.enabled=1,并在表单中添加PHPSESSID和UPLOAD_PROGRESS前缀的隐藏字段。

PHP本身不处理上传进度,得靠前端+服务端配合
PHP在接收到完整文件前无法知道上传进度——$_FILES只在上传完成时才填充。所以所谓“PHP实现进度条”,本质是让前端能实时读取上传状态,而PHP只是提供一个可被轮询或监听的进度存储通道。
关键点在于:必须启用session.upload_progress.enabled = 1(PHP ≥ 5.4),且表单需带特定隐藏字段触发该机制。
- 确保
php.ini中开启:session.upload_progress.enabled = On、session.upload_progress.cleanup = Off(调试时建议关掉自动清理) - 表单必须用
POST,enctype="multipart/form-data",且包含名为PHPSESSID的隐藏字段(值为session_id())和UPLOAD_PROGRESS前缀的隐藏字段(如<input type="hidden" name="PHPSESSID" value="<?= session_id() ?>"><input type="hidden" name="UPLOAD_PROGRESS" value="12345">) - 前端不能用
fetch或XMLHttpRequest直接发multipart——得用FormData+XMLHttpRequest.upload.onprogress,否则拿不到原生进度事件
前端用XMLHttpRequest.upload.onprogress监听原生进度
这是最可靠、无需轮询的方式,但仅适用于现代浏览器,且必须避开fetch(它不暴露上传过程)。
示例核心逻辑:
立即学习“PHP免费学习笔记(深入)”;
const form = document.querySelector('form');
form.addEventListener('submit', function(e) {
e.preventDefault();
const xhr = new XMLHttpRequest();
xhr.open('POST', 'upload.php');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total * 100).toFixed(1);
document.getElementById('progress').innerText = `${percent}%`;
document.getElementById('bar').style.width = `${percent}%`;
}
};
xhr.onload = function() {
console.log(xhr.responseText);
};
xhr.send(new FormData(form));
});-
xhr.upload.onprogress只在发送阶段触发,e.loaded是已发送字节数,e.total是总大小(仅当e.lengthComputable === true时有效) - 不要在
onprogress里频繁操作DOM,尤其避免重排;用requestAnimationFrame节流更稳妥 - 如果后端用了Nginx,要确认
client_max_body_size和client_body_timeout设置合理,否则大文件上传中途可能被代理截断,导致进度卡住
用session.upload_progress读取进度(PHP端)
当表单含UPLOAD_PROGRESS字段且session.upload_progress.enabled开启时,PHP会在$_SESSION中写入一个以upload_progress_开头的键,值为数组,含bytes_uploaded、bytes_total、done等字段。
注意:这个数据只在上传进行中存在,且默认上传完成后立即被清理(session.upload_progress.cleanup = On)。
- PHP脚本需先调用
session_start()才能读到$_SESSION - 进度键名由
session.upload_progress.prefix+ 表单中UPLOAD_PROGRESS字段的值拼成,默认是upload_progress_+ 字段值;比如表单里<input name="UPLOAD_PROGRESS" value="mykey">,则PHP里查$_SESSION['upload_progress_mykey'] - 若想用AJAX轮询进度(比如兼容老浏览器),可单独写个
progress.php返回JSON:echo json_encode($_SESSION['upload_progress_'.$key] ?? ['done' => true]);,但要注意并发读写session可能导致阻塞
常见失败原因:Nginx/FPM配置与Session冲突
即使PHP配置正确,上传进度仍不更新,大概率是Web服务器或PHP-FPM层拦截/重置了session或进度数据。
- Nginx默认会缓存整个请求体,导致
upload_progress无法实时写入session;需在location块加proxy_buffering off;(反向代理场景)或确认未启用client_body_in_file_only - PHP-FPM若配置了
process.max过低或pm = static且pm.max_children不足,上传过程中worker被占满,新进度查询请求会被排队甚至超时 - 多个上传同时进行时,不同请求可能写入同一个session ID下的不同进度键,但若前端没传唯一
UPLOAD_PROGRESS值,所有上传会共用一个键,互相覆盖 - Chrome对同域下大量并发XMLHttpRequest有连接数限制(通常6个),上传大文件时若同时发起其他AJAX请求,可能被挂起,看起来像进度停滞
真正卡住的地方往往不在PHP代码里,而在Nginx的buffer策略、FPM的进程模型,或者前端没正确生成唯一进度键——这些细节比写几行$_SESSION读取逻辑重要得多。



















