一、为什么PHP默认限制上传文件大小

咱们做Web开发的,经常遇到用户要上传文件的需求。但新手经常会碰到这样的问题:明明代码写得好好的,小文件能上传,大文件就不行。这其实是因为PHP默认设置了一个上传文件大小的限制。

PHP这个限制主要出于两个考虑:

  1. 防止服务器被大文件拖垮
  2. 避免恶意用户上传超大文件消耗服务器资源

默认情况下,PHP允许的最大上传文件大小是2MB。这个值在php.ini配置文件中通过upload_max_filesize参数设置。同时还有个post_max_size参数,它限制了整个POST请求的大小,这个值必须大于等于upload_max_filesize。

二、如何修改PHP上传限制

1. 修改php.ini配置文件(推荐)

最彻底的方法是直接修改php.ini文件。找到以下三个参数:

; 允许上传的最大文件大小
upload_max_filesize = 20M

; POST数据的最大大小(必须 ≥ upload_max_filesize)
post_max_size = 21M

; 脚本执行的最长时间(秒)
max_execution_time = 300

修改后需要重启Web服务器(如Apache或Nginx)才能生效。

2. 通过.htaccess文件修改

如果你没有服务器权限,可以在项目根目录的.htaccess文件中添加:

php_value upload_max_filesize 20M
php_value post_max_size 21M
php_value max_execution_time 300

3. 运行时通过ini_set()修改

在PHP脚本中临时修改:

<?php
// 设置上传限制(仅在当前脚本有效)
ini_set('upload_max_filesize', '20M');
ini_set('post_max_size', '21M');
ini_set('max_execution_time', '300');
?>

不过要注意,upload_max_filesize和post_max_size不能通过ini_set()修改,必须在php.ini或.htaccess中设置。

三、完整文件上传示例代码

下面是一个完整的PHP文件上传处理示例(技术栈:PHP 7.4+):

<?php
// 检查是否是POST请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 检查是否有文件上传
    if (isset($_FILES['uploaded_file'])) {
        $file = $_FILES['uploaded_file'];
        
        // 检查上传错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            handleUploadError($file['error']);
            exit;
        }
        
        // 验证文件类型(示例允许图片和PDF)
        $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
        if (!in_array($file['type'], $allowedTypes)) {
            echo '错误: 只允许上传JPEG, PNG图片或PDF文件';
            exit;
        }
        
        // 验证文件大小(最大10MB)
        $maxSize = 10 * 1024 * 1024; // 10MB
        if ($file['size'] > $maxSize) {
            echo '错误: 文件大小不能超过10MB';
            exit;
        }
        
        // 设置上传目录(确保有写入权限)
        $uploadDir = __DIR__ . '/uploads/';
        if (!is_dir($uploadDir)) {
            mkdir($uploadDir, 0755, true);
        }
        
        // 生成唯一文件名防止覆盖
        $filename = uniqid() . '_' . basename($file['name']);
        $destination = $uploadDir . $filename;
        
        // 移动临时文件到目标位置
        if (move_uploaded_file($file['tmp_name'], $destination)) {
            echo '文件上传成功: ' . htmlspecialchars($filename);
        } else {
            echo '错误: 文件保存失败';
        }
    }
}

/**
 * 处理各种上传错误
 */
function handleUploadError($errorCode) {
    switch ($errorCode) {
        case UPLOAD_ERR_INI_SIZE:
            echo '错误: 文件大小超过服务器限制';
            break;
        case UPLOAD_ERR_FORM_SIZE:
            echo '错误: 文件大小超过表单限制';
            break;
        case UPLOAD_ERR_PARTIAL:
            echo '错误: 文件只有部分被上传';
            break;
        case UPLOAD_ERR_NO_FILE:
            echo '错误: 没有文件被上传';
            break;
        case UPLOAD_ERR_NO_TMP_DIR:
            echo '错误: 服务器缺少临时文件夹';
            break;
        case UPLOAD_ERR_CANT_WRITE:
            echo '错误: 写入磁盘失败';
            break;
        case UPLOAD_ERR_EXTENSION:
            echo '错误: 文件上传被PHP扩展阻止';
            break;
        default:
            echo '错误: 未知上传错误';
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>文件上传示例</title>
</head>
<body>
    <form action="" method="post" enctype="multipart/form-data">
        <label for="file">选择文件(最大10MB):</label>
        <input type="file" name="uploaded_file" id="file" required>
        <button type="submit">上传</button>
    </form>
</body>
</html>

四、进阶技巧与注意事项

1. 分块上传大文件

对于超大文件(如视频),可以考虑实现分块上传。这里简单介绍原理:

// 前端将文件分割成多个块
// 每个块单独上传,附带块序号和总块数信息
$chunkNumber = $_POST['chunkNumber'];
$totalChunks = $_POST['totalChunks'];

// 服务器端接收并暂存每个块
$tempDir = __DIR__ . '/temp_uploads/';
$chunkFile = $tempDir . 'chunk_' . $chunkNumber;

move_uploaded_file($_FILES['file']['tmp_name'], $chunkFile);

// 当所有块都上传完成后,合并文件
if ($chunkNumber == $totalChunks) {
    $finalFile = __DIR__ . '/uploads/final_file';
    for ($i = 1; $i <= $totalChunks; $i++) {
        $chunk = file_get_contents($tempDir . 'chunk_' . $i);
        file_put_contents($finalFile, $chunk, FILE_APPEND);
        unlink($tempDir . 'chunk_' . $i); // 删除临时块
    }
}

2. 安全注意事项

  1. 文件类型验证:不要仅依赖$_FILES['type'],因为它可以被伪造。应该使用finfo_file()函数检测实际文件类型:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
  1. 文件重命名:不要直接使用用户上传的文件名,防止目录遍历攻击。

  2. 权限设置:上传目录不应有执行权限,通常设置为755。

  3. 病毒扫描:对上传文件进行病毒扫描是个好习惯。

3. 性能优化建议

  1. 对于高并发上传场景,可以考虑:

    • 使用Nginx的upload模块直接处理上传
    • 将上传目录放在内存文件系统(tmpfs)中
    • 使用CDN处理文件分发
  2. 监控上传目录大小,定期清理旧文件。

五、常见问题解答

Q: 修改了php.ini但限制仍然没变? A: 1) 确认修改的是正确的php.ini文件(通过phpinfo()查看) 2) 重启Web服务器 3) 检查是否有.htaccess或ini_set()覆盖了设置

Q: 上传进度如何显示? A: 可以使用PHP的session.upload_progress功能,或前端实现进度条。

Q: 为什么上传到一定大小就中断? A: 可能是以下原因:

  • PHP的max_execution_time超时
  • Web服务器的请求超时设置
  • 客户端网络问题

六、总结

处理PHP文件上传大小限制看似简单,但实际上需要考虑很多因素。最佳实践包括:

  1. 合理设置服务器级别的上传限制
  2. 在应用层面做二次验证
  3. 注意文件上传的安全性
  4. 对大文件上传采用分块策略
  5. 做好错误处理和用户反馈

记住,文件上传是Web应用中最常见的安全漏洞来源之一,一定要谨慎处理。希望本文能帮助你彻底解决PHP文件上传的各种问题。