文件上傳是所有web應用中最常見的功能,而PHP實現這一功能也非常的簡單,只需要前端設置表單的 enctype 值爲 multipart/form-data 之後,我們就可以通過 $_FILES 獲得表單中的 file 控件中的內容。
同時,我們還可以將 file 控件的名稱寫成帶 [] 的數組形式,這樣我們就可以接收到多個上傳的文件。比如下面這個測試用的表單:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="" enctype="multipart/form-data" method="post">
myfile1:<input type="file" name="myfile[]"/><br/>
myfile2:<input type="file" name="myfile[a][]"/><br/>
myfile3:<input type="file" name="myfile[a][b][]"/><br/>
myfile4:<input type="file" name="myfile[c][]"/><br/>
myfile5:<input type="file" name="myfile[]"/><br/>
myfile6:<input type="file" name="myfile[][]"/><br/>
<br/>
newfile1:<input type="file" name="newfile[][]"/><br/>
newfile2:<input type="file" name="newfile[s]"/><br/>
singlefile: <input type="file" name="singlefile"/><br/>
<input type="submit" value="submit"/>
</form>
</body>
</html>
一共有9個 file 控件,其中 myfile 和 newfile 都是數組類型的表單名,而 singlefile 則是一個單獨的。先簡單的看一下 $_FILES 所獲得的內容。
print_r($_FILES);
Array
(
[myfile] => Array
(
[name] => Array
(
[0] => 2591d8b3eee018a0a84f671933ab6c74.png
[a] => Array
(
[0] => 12711584942474_.pic_hd 1.jpg
[b] => Array
(
[0] => 12721584942474_.pic_hd 1.jpg
)
)
[c] => Array
(
[0] => 12731584942474_.pic_hd.jpg
)
[1] => background1.jpg
[2] => Array
(
[0] => adliu_pip_data.xlsx
)
)
[type] => Array
(
[0] => image/png
[a] => Array
(
[0] => image/jpeg
[b] => Array
(
[0] => image/jpeg
)
)
[c] => Array
(
[0] => image/jpeg
)
[1] => image/jpeg
[2] => Array
(
[0] => application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
)
)
[tmp_name] => Array
(
[0] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phphD88ZY
[a] => Array
(
[0] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpNY8MzY
[b] => Array
(
[0] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/php3MX5tk
)
)
[c] => Array
(
[0] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpjgrHMj
)
[1] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phppXRtnc
[2] => Array
(
[0] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpekSY1M
)
)
[error] => Array
(
[0] => 0
[a] => Array
(
[0] => 0
[b] => Array
(
[0] => 0
)
)
[c] => Array
(
[0] => 0
)
[1] => 0
[2] => Array
(
[0] => 0
)
)
[size] => Array
(
[0] => 4973
[a] => Array
(
[0] => 3007
[b] => Array
(
[0] => 1156
)
)
[c] => Array
(
[0] => 6068
)
[1] => 393194
[2] => Array
(
[0] => 36714
)
)
)
[newfile] => Array
(
[name] => Array
(
[0] => Array
(
[0] => 數據列表 (2).xlsx
)
[s] => background1.jpg
)
[type] => Array
(
[0] => Array
(
[0] => application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
)
[s] => image/jpeg
)
[tmp_name] => Array
(
[0] => Array
(
[0] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phplSsRfM
)
[s] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpuQAvRb
)
[error] => Array
(
[0] => Array
(
[0] => 0
)
[s] => 0
)
[size] => Array
(
[0] => Array
(
[0] => 77032
)
[s] => 393194
)
)
[singlefile] => Array
(
[name] => timg (8).jpeg
[type] => image/jpeg
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpxtSQ4J
[error] => 0
[size] => 10273
)
)
看出有什麼問題了嗎?
$_FILE['singlefile']['name'];
$_FILE['singlefile']['type'];
$_FILE['singlefile']['tmp_name'];
$_FILE['singlefile']['error'];
$_FILE['singlefile']['error'];
$_FILE['myfile']['name']['a']['b'][0];
$_FILE['myfile']['type']['a']['b'][0];
$_FILE['myfile']['tmp_name']['a']['b'][0];
$_FILE['myfile']['error']['a']['b'][0];
$_FILE['myfile']['error']['a']['b'][0];
單個表單是一個 singlefile 爲鍵名的數組,裏面是對應的 name 、 type 等屬性。這個非常簡單也清晰明瞭,但是數組形式上傳的內容就比較坑了,每一個屬性下面都有多個值,而且這些值還有可能是嵌套的數組。就比如說我們要獲得 myfile[a][b][] 的上傳文件內容,我們就要通過 \_FILE['myfile']['type']['a']['b'][0] 這樣的形式獲得相關的內容。這個可真的不是很友好,那麼我們今天的主題就來了,我們把這種內容進行一下格式化,讓他變成和 singlefile 類似的結構,也就是一個文件的相關內容都在一個鍵名結構下,比如 myfile[a][b][] 的內容就全部都在 $_FILE['myfile'][a][b][0]下面。
$files = [];
// 開始數據格式化
foreach ($_FILES as $uploadKey => $uploadFiles) {
// 需要將 $_FILES 中的五個字段都拿出來
$files[$uploadKey] = formatUploadFiles($uploadFiles['name'], $uploadFiles['type'], $uploadFiles['tmp_name'], $uploadFiles['error'], $uploadFiles['size']);
}
// 格式化上傳文件數組
function formatUploadFiles($fileNamesArray, $type, $tmp_name, $error, $size)
{
$tmpFiles = [];
// 文件名是否是數組,如果不是數組,就是單個文件上傳
if (is_array($fileNamesArray)) {
// 數組形式上傳
foreach ($fileNamesArray as $idx => $fileName) {
// 如果還是嵌套的數組,遞歸遍歷接下來的內容
if (is_array($fileName)) {
$tmpFiles[$idx] = formatUploadFiles($fileName, $type[$idx] ?? [], $tmp_name[$idx] ?? [], $error[$idx] ?? [], $size[$idx] ?? []);
} else {
// 組合多維的格式化內容
$tmpFiles[$idx] = [
'name' => $fileName,
'type' => $type[$idx] ?? '',
'tmp_name' => $tmp_name[$idx] ?? '',
'error' => $error[$idx] ?? '',
'size' => $size[$idx] ?? '',
];
}
}
} else {
// 組合單個的內容
$tmpFiles = [
'name' => $fileName,
'type' => $type ?? '',
'tmp_name' => $tmp_name ?? '',
'error' => $error ?? '',
'size' => $size ?? '',
];
}
return $tmpFiles;
}
print_r($files);
代碼還是非常好理解的,就是通過一段遞歸來遍歷整個 $_FILES 目錄樹,相當於一個深度遍歷。當然,這樣也會帶來性能的下降,畢竟是需要進行循環+遞歸的遍歷。不過好在大部分情況下我們上傳的文件並不會那麼的多。不過反過來說,如果不事先進行格式化,當你想獲得所有的上傳內容時,一樣還是需要進行多層或者遞歸遍歷的。
接下來我們看看格式化之後的輸出:
Array
(
[myfile] => Array
(
[0] => Array
(
[name] => 2591d8b3eee018a0a84f671933ab6c74.png
[type] => image/png
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpV7A2yC
[error] => 0
[size] => 4973
)
[a] => Array
(
[0] => Array
(
[name] => 12711584942474_.pic_hd 1.jpg
[type] => image/jpeg
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/php5q2d1Z
[error] => 0
[size] => 3007
)
[b] => Array
(
[0] => Array
(
[name] => 12721584942474_.pic_hd 1.jpg
[type] => image/jpeg
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpdvv8No
[error] => 0
[size] => 1156
)
)
)
[c] => Array
(
[0] => Array
(
[name] => 12731584942474_.pic_hd.jpg
[type] => image/jpeg
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/php9tfGmp
[error] => 0
[size] => 6068
)
)
[1] => Array
(
[name] => background1.jpg
[type] => image/jpeg
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phplUVpzA
[error] => 0
[size] => 393194
)
[2] => Array
(
[0] => Array
(
[name] => adliu_pip_data.xlsx
[type] => application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpNRtiaC
[error] => 0
[size] => 36714
)
)
)
[newfile] => Array
(
[0] => Array
(
[0] => Array
(
[name] => 數據列表 (2).xlsx
[type] => application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpBLG7aG
[error] => 0
[size] => 77032
)
)
[s] => Array
(
[name] => background1.jpg
[type] => image/jpeg
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpjyqCFY
[error] => 0
[size] => 393194
)
)
[singlefile] => Array
(
[name] =>
[type] => image/jpeg
[tmp_name] => /private/var/folders/wj/t2z1cfhs0m9gq48krm8nc0vm0000gn/T/phpuYJXiE
[error] => 0
[size] => 10273
)
)
和上面原始的 $_FILES 相比是不是清晰明瞭的很多?這回我們如果需要 myfile[a][b][] 裏面全部的內容時,就可以使用下面的方式方便的獲取了:
$files['myfile']['a']['b'][0]['name'];
$files['myfile']['a']['b'][0]['type'];
$files['myfile']['a']['b'][0]['tmp_name'];
$files['myfile']['a']['b'][0]['error'];
$files['myfile']['a']['b'][0]['size'];
當然,這種需求在我們的日常工作中並不多見,這裏也只是提供一個思路,將數據提前轉化成我們需要的格式是一種非常好的習慣,能夠讓我們的後續操作變得非常簡單。
測試代碼:
參考文檔:
https://www.php.net/manual/zh/features.file-upload.php
各自媒體平臺均可搜索【硬核項目經理】