可以对 input 域使用不同的 name 来上传多个文件。
PHP 支持同时上传多个文件并将它们的信息自动以数组的形式组织。要完成这项功能,需要在 HTML 表单中对文件上传域使用和多选框与复选框相同的数组式提交语法。
Note:
对多文件上传的支持是在 PHP 3.0.10 版本添加的。
Example #1 上传多个文件
<form action="file-upload.php" method="post" enctype="multipart/form-data"> Send these files:<br /> <input name="userfile[]" type="file" /><br /> <input name="userfile[]" type="file" /><br /> <input type="submit" value="Send files" /> </form>
当以上表单被提交后,数组 $_FILES['userfile'],$_FILES['userfile']['name'] 和 $_FILES['userfile']['size'] 将被初始化(在 PHP 4.1.0 以前版本是 $HTTP_POST_FILES)。如果 register_globals 的设置为 on,则和文件上传相关的全局变量也将被初始化。所有这些提交的信息都将被储存到以数字为索引的数组中。
例如,假设名为 /home/test/review.html 和 /home/test/xwp.out 的文件被提交,则 $_FILES['userfile']['name'][0] 的值将是 review.html,而 $_FILES['userfile']['name'][1] 的值将是 xwp.out。类似的,$_FILES['userfile']['size'][0] 将包含文件 review.html 的大小,依此类推。
此外也同时设置了 $_FILES['userfile']['name'][0],$_FILES['userfile']['tmp_name'][0],$_FILES['userfile']['size'][0] 以及 $_FILES['userfile']['type'][0]。
molnar at mediacube dot sk (2012-07-17 12:32:07)
I'am using this to normalize $_FILES array. It's short and clean, normalizes fields with single upload or multiupload. It uses explicit array typecast:
<?php
function getNormalizedFILES()
{
$newfiles = array();
foreach($_FILES as $fieldname => $fieldvalue)
foreach($fieldvalue as $paramname => $paramvalue)
foreach((array)$paramvalue as $index => $value)
$newfiles[$fieldname][$index][$paramname] = $value;
return $newfiles;
}
?>
Donatas Olseviius (2012-03-20 07:53:16)
This method fixes $_FILES array when uploading multiple files.
<?php
public static function fixGlobalFilesArray($files) {
$ret = array();
if(isset($files['tmp_name']))
{
if (is_array($files['tmp_name']))
{
foreach($files['name'] as $idx => $name)
{
$ret[$idx] = array(
'name' => $name,
'tmp_name' => $files['tmp_name'][$idx],
'size' => $files['size'][$idx],
'type' => $files['type'][$idx],
'error' => $files['error'][$idx]
);
}
}
else
{
$ret = $files;
}
}
else
{
foreach ($files as $key => $value)
{
$ret[$key] = self::fixGlobalFilesArray($value);
}
}
return $ret;
}
?>
timspeelman at live dot nl (2011-11-18 15:48:16)
The cleanest way to rearrange the $_FILES
<?php
function rearrange( $arr ){
foreach( $arr as $key => $all ){
foreach( $all as $i => $val ){
$new[$i][$key] = $val;
}
}
return $new;
}
?>
lugae at gmx dot ch (2011-08-03 14:55:03)
Multiple upload might not work if you use a table for displaying your form inputs when <form> element is inside the <table> element. In this case only the first file will be uploaded.
Put the <form> element outside the <table> element to get it to work.
contato at dgmike dot com dot br (2011-07-19 11:33:03)
I prefer something like this!
<?php
public function arrayImages ( &$file_post )
{
if( empty( $file_post ) ) {
return $file_post;
}
if( 'array'!==gettype($file_post['name']) ) {
return $file_post;
}
$keys = array_keys($file_post['name']);
$file_array = array();
foreach ($keys as $key) {
foreach ($file_post as $res=>$item) {
$file_array[$key][$res] = $item[$key];
}
}
return $file_array;
}
?>
leehowarth1 at live dot co dot uk (2011-06-02 23:04:41)
How to handle multiple $_FILES is quite easy i would use continue and build a further array for error messages to be displayed for output to the user, this is very good as say if you accept 4 of the 10 files the user uploads u can show 6 messages stating there file extensions are not allowed and then say a few was uploaded.
<?php
$uploaded = 0;
$message = array();
foreach ($_FILES['image']['name'] as $i => $name) {
if ($_FILES['image']['error'][$i] == 4) {
continue;
}
if ($_FILES['image']['error'][$i] == 0) {
if ($_FILES['image']['size'][$i] > 99439443) {
$message[] = "$name exceeded file limit.";
continue;
}
/*
* Check File Extension
* Move File
*/
$uploaded++;
}
}
echo $uploaded . ' files uploaded.';
foreach ($message as $error) {
echo $error;
}
?>
Enjoy!
sque (2011-05-15 12:23:33)
This a solution, using php closures (version >= 5.3), to convert the weird indexes schema of $_FILES. Very useful in case you have multiple indexes in the name of the html input element (e.g name="form1[set1][file1][]"), or if you just want to handle multiple files per element.
<?php
/**
* Fix the $_FILES order by converting from wierd schema
* [firstel]["name"][subel][0], [firstel]["size"][subel][0] ... [firstel]["error"][subel][0]
*
* to a more straightforward one.
* [firstel][subel][0]["name"], [firstel][subel][0]["size"] ... [firstel][subel][0]["error"]
*
* @author Sque
*/
$fix_files_keys = function($files) use (& $fix_files_keys)
{
if (isset($files['name'], $files['tmp_name'], $files['size'], $files['type'], $files['error'])){
// Multiple values for post-keys indexes
$move_indexes_right = function($files) use(& $move_indexes_right)
{
$results = array();
foreach($files['name'] as $index => $name) {
$reordered = array(
'name' => $files['name'][$index],
'tmp_name' => $files['tmp_name'][$index],
'size' => $files['size'][$index],
'type' => $files['type'][$index],
'error' => $files['error'][$index],
);
// If this is not leaf do it recursivly
if (is_array($name))
$reordered = $move_indexes_right($reordered);
$results[$index] = $reordered;
}
return $results;
};
return $move_indexes_right($files);
}
// Re order pre-keys indexes
array_walk($files, function(&$sub) use(& $fix_files_keys) {
$sub = $fix_files_keys($sub);
});
return $files;
};
?>
Corey Ballou (2010-03-25 13:10:39)
Here is a function to fix the indices of a multi-dimensional for easier parsing when dealing with file uploads. It takes a single $_FILES field array as a parameter and separates each individual uploaded file by numeric key. This allows for iterating like:
<?php
fixFilesArray($_FILES['array_of_files']);
foreach ($_FILES['array_of_files'] as $position => $file) {
// should output array with indices name, type, tmp_name, error, size
var_dump($file);
}
?>
Here's the code:
<?php
/**
* Fixes the odd indexing of multiple file uploads from the format:
*
* $_FILES['field']['key']['index']
*
* To the more standard and appropriate:
*
* $_FILES['field']['index']['key']
*
* @param array $files
* @author Corey Ballou
* @link http://www.jqueryin.com
*/
function fixFilesArray(&$files)
{
$names = array( 'name' => 1, 'type' => 1, 'tmp_name' => 1, 'error' => 1, 'size' => 1);
foreach ($files as $key => $part) {
// only deal with valid keys and multiple files
$key = (string) $key;
if (isset($names[$key]) && is_array($part)) {
foreach ($part as $position => $value) {
$files[$position][$key] = $value;
}
// remove old key reference
unset($files[$key]);
}
}
}
?>
christiaan at baartse dot nl (2010-02-23 08:13:46)
After trying dozens of ways that are supposed to fix the wonkyness of the $_FILES array I didn't find any that could work with a input name like: userfile[christiaan][][][is][gaaf][]
So I came up with this class
<?php
/**
* A class that takes the pain out of the $_FILES array
* @author Christiaan Baartse <christiaan@baartse.nl>
*/
class UploadedFiles extends ArrayObject
{
public function current() {
return $this->_normalize(parent::current());
}
public function offsetGet($offset) {
return $this->_normalize(parent::offsetGet($offset));
}
protected function _normalize($entry) {
if(isset($entry['name']) && is_array($entry['name'])) {
$files = array();
foreach($entry['name'] as $k => $name) {
$files[$k] = array(
'name' => $name,
'tmp_name' => $entry['tmp_name'][$k],
'size' => $entry['size'][$k],
'type' => $entry['type'][$k],
'error' => $entry['error'][$k]
);
}
return new self($files);
}
return $entry;
}
}
?>
This allows you to access a file uploaded using the following inputtype
<input type="file" name="userfile[christiaan][][][is][gaaf][]" />
like
<?php
$files = new UploadedFiles($_FILES);
var_dump($files['userfile']['christiaan'][0][0]['is']['gaaf'][0]);
// or
foreach($files['userfile']['christiaan'][0][0]['is']['gaaf'] as $file) {
var_dump($file);
}
?>
thomas dot hebinck at digionline dot de (2010-01-26 02:27:17)
HTML5 defines <input type="file" multiple="true" name="files[]"> for uploading multiple files (implemented in Firefox 3.6 and Chrome 4 so far), rather than <input type="file" multiple="true" name="files[]" />.
jess at semlabs dot co dot uk (2009-03-03 12:32:27)
If you try and upload files with multi-dimensional names like this:
<input type="file" name="submission[screenshot]" />
<input type="file" name="other[dem][][img][]" />
You will get an unexpected format like this:
<?php
array(
'submission' => array
(
'name' => array( 'screenshot' => 'monster_wallpaper.jpg' ),
'type' => array( 'screenshot' => 'image/jpeg' ),
'tmp_name' => array( 'screenshot' => '/tmp/php48lX2Y' ),
'error' => array( 'screenshot' => 0 ),
'size' => array( 'screenshot' => 223262 ),
),
....
?>
You can use the following function to re-format the array recursively in the usual format:
<?php
function format_files_array( $files, $name = null, &$new = false, $path = false ){
$names = array( 'name' => 'name', 'type' => 'type', 'tmp_name' => 'tmp_name', 'error' => 'error', 'size' => 'size' );
foreach( $files as $key => &$part )
{
$key = ( string ) $key;
if( in_array( $key, $names ) )
$name = $key;
if( !in_array( $key, $names ) )
$path[] = $key;
if( is_array( $part ) )
$part = format_files_array( $part, $name, $new, $path );
elseif( !is_array( $part ) )
{
$current =& $new;
foreach( $path as $p )
$current =& $current[$p];
$current[$name] = $part;
unset( $path );
$name = null;
}
}
return $new;
}
?>
tom at nono dot be (2008-03-18 07:04:25)
Handling multiple uploads can be a lot more user friendly with a little help of javascript and form posting to iFrame...
To make it all a little bit more edible, you can use AJAX and divs to provide loader gifs etc...
Work with separate forms; note the target of each form = csr. It is the ID of an iFrame somewhere on the page. I call it CSR as short for Client-Server-Request.
Also note the hidden input formId that we will use in the php upload handler.
disable submitting the form using onsubmit="return false"...
Finally a button outside the form structure starting a javascript function onclick="upload(document.form_0, document.loader_0)"
<form onsubmit="return false" id="file_0" name="file_0" action="upload.php" target="csr" enctype="multipart/form-data" method="post" style="margin:px; padding:0px">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>File 1:</td>
<td>
<input type="hidden" id="formId" name="formId" value="0" />
<input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="1000000" />
<input type="file" id="userFile" name="userFile" class="invulveld100pct" />
</td>
<td>
<div id="loader_0"></div>
</td>
</tr>
</table>
</form>
<form onsubmit="return false" id="file_1" name="file_1" action="upload.php" target="csr" enctype="multipart/form-data" method="post" style="margin:px; padding:0px">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>File 2:</td>
<td>
<input type="hidden" id="formId" name="formId" value="1" />
<input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="1000000" />
<input type="file" id="userFile" name="userFile" class="invulveld100pct" />
</td>
<td>
<div id="loader_0"></div>
</td>
</tr>
</table>
</form>
<input type="button" onclick="upload(document.form_0, document.loader_0)" />
<iframe id="csr" name="csr" height="1" width="1" style="border:0px none"></iframe>
<!--//The javascript://-->
<script type="javascript">
function upload(form, loader){
//only do this if the form exists
if(form){
//display a loadbar
loader.innerHTML = 'loading.gif';
form.submit();
}
}
</script>
The php upload handler upload.php
Remember, your page will not refresh because the post is sent to your CSR iFrame.
<?php
//there are enough examples around to handle the upload...
//the only important difference is the error reporting and the starting of the next form upload...
//presume $uploadOk is a boolean that is true if the upload succeeds; false if it fails...
//note the use of "parent" in the outputted javascript... the script is outputted into the CSR iFrame... therefor it needs parent to acces dom objects and javascript of the main page.
$currentFormId = $_POST['formId'];
$nextFormId = $_POST['formId'] + 1;
echo "<script type=\"javascript\">";
//change the content of your loader div to a desired image
if($uploadOk){
echo "parent.loader_{$currentFormId}.innerHTML = 'uploadOk.gif';";
} else {
echo "parent.loader_{$currentFormId}.innerHTML = 'uploadNotOk.gif';";
}
//submit the next form... the javascript function will only perform it if the form exists.
echo "parent.upload(document.form_{$nextFormId}, document.loader_{$nextFormId});";
echo "</script>";
?>
This is just a quick draft of how to handle multiple files this way and I'm sure you would need to optimize the workflow to fit your needs, but the benifit of working this way is although your user still chooses multiple files, they are in fact posted one by one... This means your MAX_FILE_SIZE is determined for each file separately as opposed to the combined size of all files in one post.
I hope this is a contribution...
hotmail.com[at]notdefix (2007-09-27 07:47:33)
With multiple file uploads
post_max_size: the total amount of data posted by the client (all files, and all other form field)
upload_max_filesize: the maximum size of 1 single file. (just like <input type="hidden" name="MAX_FILE_SIZE" value="..."/>)
so, with the directives:
post_max_size 25M
upload_max_filesize 2M
you can send 12 files of up to 2 MB and use up to 1 MB for your additional form-values.
As long as you read only a single copy of 1 file into memory, the memory_limit directive can be held reasonable small as well.
captlid at yahoo dot com (2007-02-23 06:08:17)
I noticed that the manual does not have a basic processing script for testing purposes to process multiple file uploads. It took me about an hour to figure this out so I figured it should help some newbie.
Also on windows, the OS does not care if you use backslashes and front slashes while writing up a directory path. So for compatibility with *nix just keep it as a foward slash.
(Tested with php5, php4, apache 1.3x and 2x, on winxp pro, win2k pro and win98se and freebsd.)
The script is kept simple for illustration purposes. Dont use it in a production environment.
The form
<form method="post" action="" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="500000">
<?
for($i = 1; $i <= $_POST[totalfiles]; $i++) { echo $i.'. <input type="file" name="photos[]"><br>'."\n"; }
?>
<input type="submit" name="sendfiles" value="Send Files"></form>
The processing script, for simplicities sake in the same file.
if ($_POST[sendfiles]) {
print_r($_POST); echo '<pre>'; print_r($_FILES); echo '</pre>';
$uploaddir = getcwd().'/photos/; //a directory inside
foreach ($_FILES[photos][name] as $key => $value) {
$uploadfile = $uploaddir . basename($_FILES[photos][name][$key]);
//echo $uploadfile;
if (move_uploaded_file($_FILES['photos']['tmp_name'][$key], $uploadfile)) { echo $value . ' uploaded<br>'; }
}
}
Bob Doe (2005-08-08 14:17:49)
Here is a the simple test form I needed, pieced togther from 2 or 3 posts in the documentation elsewhere.
<html>
<head>
<title>HTML Form for uploading image to server</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data">
<p>Pictures:
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="Send" />
</p>
</form>
<?php
//places files into same dir as form resides
foreach ($_FILES["pictures"]["error"] as $key => $error) {
if ($error == UPLOAD_ERR_OK) {
echo"$error_codes[$error]";
move_uploaded_file(
$_FILES["pictures"]["tmp_name"][$key],
$_FILES["pictures"]["name"][$key]
) or die("Problems with upload");
}
}
?>
</body>
</html>
(2005-07-28 19:50:02)
re: phpuser's comment
I found that if instead of the form structure at the top of the page use one like this:
<form action="file-upload.php" method="post" enctype="multipart/form-data">
Send these files:<br />
<input name="userfile1" type="file" /><br />
<input name="userfile2" type="file" /><br />
<input type="submit" value="Send files" />
</form>
Notice the names are unique and not an array element. Now the array is structured more like phpuser would like. I did this and used...
foreach ($_FILES as $file) { ... }
without issue.
sgoodman_at_nojunk_immunetolerance.org (2005-06-17 07:03:02)
Re: phpuser_at_gmail's comment, a simpler way to have create that data structure is to name your HTML file inputs different names. If you want to upload multiple files, use:
<input type=file name=file1>
<input type=file name=file2>
<input type=file name=file3>
etc...
Each field name will be a key in the $_FILES array.
bishop (2005-06-02 11:27:56)
Elaboration on phpuser at gmail dot com reArrayFiles() function (which assumed sequential, integer keys and uni-dimensional), this function will work regardless of key and key depth:
<?php
// information grouper
function groupFileInfoByVariable(&$top, $info, $attr) {
if (is_array($info)) {
foreach ($info as $var => $val) {
if (is_array($val)) {
groupFileInfoByVariable($top[$var], $val, $attr);
} else {
$top[$var][$attr] = $val;
}
}
} else {
$top[$attr] = $info;
}
return true;
}
// usage
$newOrdering = array ();
foreach ($_FILES as $var => $info) {
foreach (array_keys($info) as $attr) {
groupFileInfoByVariable($newOrdering, $info[$attr], $attr);
}
}
// $newOrdering holds the updated order
?>
phpuser at gmail dot com (2005-05-26 08:09:40)
When uploading multiple files, the $_FILES variable is created in the form:
Array
(
[name] => Array
(
[0] => foo.txt
[1] => bar.txt
)
[type] => Array
(
[0] => text/plain
[1] => text/plain
)
[tmp_name] => Array
(
[0] => /tmp/phpYzdqkD
[1] => /tmp/phpeEwEWG
)
[error] => Array
(
[0] => 0
[1] => 0
)
[size] => Array
(
[0] => 123
[1] => 456
)
)
I found it made for a little cleaner code if I had the uploaded files array in the form
Array
(
[0] => Array
(
[name] => foo.txt
[type] => text/plain
[tmp_name] => /tmp/phpYzdqkD
[error] => 0
[size] => 123
)
[1] => Array
(
[name] => bar.txt
[type] => text/plain
[tmp_name] => /tmp/phpeEwEWG
[error] => 0
[size] => 456
)
)
I wrote a quick function that would convert the $_FILES array to the cleaner (IMHO) array.
<?php
function reArrayFiles(&$file_post) {
$file_ary = array();
$file_count = count($file_post['name']);
$file_keys = array_keys($file_post);
for ($i=0; $i<$file_count; $i++) {
foreach ($file_keys as $key) {
$file_ary[$i][$key] = $file_post[$key][$i];
}
}
return $file_ary;
}
?>
Now I can do the following:
<?php
if ($_FILES['upload']) {
$file_ary = reArrayFiles($_FILES['ufile']);
foreach ($file_ary as $file) {
print 'File Name: ' . $file['name'];
print 'File Type: ' . $file['type'];
print 'File Size: ' . $file['size'];
}
}
?>