首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 多文件上傳核心技術解析:$_FILES 數組結構與批量處理邏輯

     編輯:PHP 綜合知識

前言

多文件上傳是表單處理的進階場景,其核心在於理解 $_FILES 超全局數組的特殊結構。本文深度解析多文件上傳時 $_FILES 的多維數組組織形式,演示標準的批量處理流程,並指出常見的遍歷陷阱與安全加固要點。

HTML 表單關鍵點

<form action="uploads.php" method="post" enctype="multipart/form-data">
    <!-- 關鍵:name 屬性以 [] 結尾,告訴 PHP 這是數組 -->
    <input type="file" name="files[]" multiple>
    <input type="file" name="files[]" multiple>
    <button type="submit">上傳</button>
</form>

關鍵屬性解析:

  • enctype="multipart/form-data":必須設置,否則文件不會上傳
  • name="files[]":方括號標記使 PHP 將同名字段組合為數組
  • multiple:HTML5 屬性,允許用戶在單個 input 中選擇多文件

$_FILES 數組結構深度解析

單文件上傳結構(對比參考)

$_FILES['file'] = [
    'name'     => 'photo.jpg',      // 原始文件名
    'type'     => 'image/jpeg',     // MIME 類型(瀏覽器提供,不可信)
    'tmp_name' => '/tmp/phpA1b2C3', // 臨時存儲路徑
    'error'    => 0,                // 錯誤代碼(0=成功)
    'size'     => 102400,           // 文件大小(位元組)
];

多文件上傳結構(核心差異:值變為數組)

$_FILES['files'] = [
    'name'     => ['photo1.jpg', 'photo2.png', 'doc.pdf'],
    'type'     => ['image/jpeg', 'image/png', 'application/pdf'],
    'tmp_name' => ['/tmp/phpA1b2C3', '/tmp/phpD4e5F6', '/tmp/phpG7h8I9'],
    'error'    => [0, 0, 0],
    'size'     => [102400, 204800, 512000],
];

結構特徵: 第一維鍵為屬性名(name/type/tmp_name/error/size),第二維為文件索引(0,1,2...)。這是「列優先」結構,而非直觀的「行優先」(每個文件一組屬性)。

標準批量處理代碼

<?php
// uploads.php

// 配置
const UPLOAD_DIR = __DIR__ . '/uploads';
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];

// 確保目錄存在
if (!is_dir(UPLOAD_DIR)) {
    mkdir(UPLOAD_DIR, 0755, true);
}

// 獲取文件數量(以 name 數組長度為準)
$fileCount = count($_FILES['files']['name'] ?? []);

$results = ['success' => [], 'failed' => []];

for ($i = 0; $i < $fileCount; $i++) {
    // 組裝單個文件信息(行優先結構,便於處理)
    $file = [
        'name'     => $_FILES['files']['name'][$i],
        'type'     => $_FILES['files']['type'][$i],
        'tmp_name' => $_FILES['files']['tmp_name'][$i],
        'error'    => $_FILES['files']['error'][$i],
        'size'     => $_FILES['files']['size'][$i],
    ];
    
    // 1. 檢查上傳錯誤
    if ($file['error'] !== UPLOAD_ERR_OK) {
        $results['failed'][] = [
            'name' => $file['name'],
            'error' => "上傳錯誤代碼: {$file['error']}"
        ];
        continue;
    }
    
    // 2. 檢查文件大小
    if ($file['size'] > MAX_SIZE) {
        $results['failed'][] = [
            'name' => $file['name'],
            'error' => '文件超過大小限制'
        ];
        continue;
    }
    
    // 3. 驗證真實 MIME 類型(防偽裝)
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $realMime = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);
    
    if (!in_array($realMime, ALLOWED_TYPES, true)) {
        $results['failed'][] = [
            'name' => $file['name'],
            'error' => "文件類型不允許: {$realMime}"
        ];
        continue;
    }
    
    // 4. 生成安全文件名
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $newName = bin2hex(random_bytes(16)) . '.' . $ext;
    $destPath = UPLOAD_DIR . '/' . $newName;
    
    // 5. 移動文件
    if (move_uploaded_file($file['tmp_name'], $destPath)) {
        $results['success'][] = [
            'original' => $file['name'],
            'saved_as' => $newName,
            'size' => $file['size'],
            'mime' => $realMime,
        ];
    } else {
        $results['failed'][] = [
            'name' => $file['name'],
            'error' => '文件保存失敗'
        ];
    }
}

// 輸出結果
header('Content-Type: application/json; charset=utf-8');
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

常見遍歷陷阱與修正

❌ 錯誤寫法:直接 foreach $_FILES['files']

<?php
// 錯誤!這會遍歷 5 個屬性鍵,而非文件
foreach ($_FILES['files'] as $key => $value) {
    // $key 依次為:name, type, tmp_name, error, size
    // $value 為對應的數組
}

❌ 錯誤寫法:假設結構為行優先

<?php
// 錯誤!$_FILES['files'] 不是 [ [file1_attrs], [file2_attrs], ... ]
foreach ($_FILES['files'] as $file) {
    // $file 會是 'name' => ['a.jpg', 'b.jpg'] 這樣的結構
}

✅ 正確寫法:按索引重組(如上文標準代碼)

<?php
$count = count($_FILES['files']['name']);
for ($i = 0; $i < $count; $i++) {
    $file = [
        'name' => $_FILES['files']['name'][$i],
        // ... 其他屬性
    ];
    // 處理 $file
}

HTML5 multiple 屬性的影響

當單個 <input type="file" name="files[]" multiple> 選擇多文件時,$_FILES 結構完全相同,仍為列優先多維數組。區別在於前端只需一個 input 元素。

安全加固清單

風險防護措施
文件名注入使用 basename() + 隨機重命名,不信任原始文件名
MIME 偽裝finfo_file() 檢測真實類型,配合擴展名白名單
大文件 DoS設置 upload_max_filesize、post_max_size、代碼層雙重限制
腳本執行上傳目錄禁用 PHP 執行權限(Nginx: location ~ \.php$ { deny all; })
路徑遍歷不使用用戶輸入路徑,固定保存目錄
臨時文件殘留move_uploaded_file 失敗時自動清理,成功後臨時文件自動刪除

進階:PSR-7 規範化上傳文件對象

現代框架(Laravel、Symfony、Slim)均封裝了 UploadedFile 物件:

<?php
// 概念示例(實際使用框架提供的實現)
class UploadedFile {
    public function getClientFilename(): string;
    public function getClientMediaType(): string;
    public function getError(): int;
    public function getSize(): int;
    public function getStream(): StreamInterface;
    public function moveTo(string $targetPath): void;
}

// 使用示例
foreach ($request->getUploadedFiles()['files'] as $file) {
    if ($file->getError() === UPLOAD_ERR_OK) {
        $file->moveTo(UPLOAD_DIR . '/' . $file->getClientFilename());
    }
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved