How do you loop through $_FILES array?
Here are the inputs I want to loop through
Main photo: <input type="file" name="image[]" />
Side photo 1: <input type="file" name="image[]" />
Side photo 2: <input type="file" name="image[]" />
Side photo 3: <input type="file" name="image[]" />
A couple weird things happened, when I uploaded nothing I use the count($_FILES['image'])
, I echoed that function, and it returns a value of 5. There should be no elements in that array. Why is there one extra input when I only have 4 files to begin with?
Now with the actually looping itse开发者_C百科lf, I try using the foreach loop, but it doesn't work.
foreach($_FILES['image'] as $files){echo $files['name']; }
Nothing came up, what I wanted to ultimately do is to loop through all images, make sure they are correct format, size, and rename each of them. But this simple foreach() loop shows that somehow I can't even loop through the $_FILES array and the count() confused me even more when it say that there are 5 elements in the array when I didn't even upload anything.
Your example form should work fine. It's just that you are expecting the structure of the $_FILES
superglobal to be different than it actually is, when using an array structure for the field names.
The structure of this multidimensional array is as followed then:
$_FILES[fieldname] => array(
[name] => array( /* these arrays are the size you expect */ )
[type] => array( /* these arrays are the size you expect */ )
[tmp_name] => array( /* these arrays are the size you expect */ )
[error] => array( /* these arrays are the size you expect */ )
[size] => array( /* these arrays are the size you expect */ )
);
Therefor count( $_FILES[ "fieldname" ] )
will yield 5
.
But counting deeper dimensions will also not produce the result you may expect. Counting the fields with count( $_FILES[ "fieldname" ][ "tmp_name" ] )
for instance, will always result in the number of file fields, not in the number of files that have actually been uploaded. You'd still have to loop through the elements to determine whether anything has been uploaded for a particular file field.
EDIT
So, to loop through the fields you would do something like the following:
// !empty( $_FILES ) is an extra safety precaution
// in case the form's enctype="multipart/form-data" attribute is missing
// or in case your form doesn't have any file field elements
if( strtolower( $_SERVER[ 'REQUEST_METHOD' ] ) == 'post' && !empty( $_FILES ) )
{
foreach( $_FILES[ 'image' ][ 'tmp_name' ] as $index => $tmpName )
{
if( !empty( $_FILES[ 'image' ][ 'error' ][ $index ] ) )
{
// some error occured with the file in index $index
// yield an error here
return false; // return false also immediately perhaps??
}
/*
edit: the following is not necessary actually as it is now
defined in the foreach statement ($index => $tmpName)
// extract the temporary location
$tmpName = $_FILES[ 'image' ][ 'tmp_name' ][ $index ];
*/
// check whether it's not empty, and whether it indeed is an uploaded file
if( !empty( $tmpName ) && is_uploaded_file( $tmpName ) )
{
// the path to the actual uploaded file is in $_FILES[ 'image' ][ 'tmp_name' ][ $index ]
// do something with it:
move_uploaded_file( $tmpName, $someDestinationPath ); // move to new location perhaps?
}
}
}
For more information see the docs.
Short function to rebuild $_FILES['files'] to some more expected structure.
function restructureFilesArray($files)
{
$output = [];
foreach ($files as $attrName => $valuesArray) {
foreach ($valuesArray as $key => $value) {
$output[$key][$attrName] = $value;
}
}
return $output;
}
just rename your fields this way
Main photo: <input type="file" name="image1" />
Side photo 1: <input type="file" name="image2" />
Side photo 2: <input type="file" name="image3" />
Side photo 3: <input type="file" name="image4" />
and then you'll be able to iterate it usual way:
foreach($_FILES as $file){
echo $file['name'];
}
I came up with a solution that works for $_FILES arrays of arbitrary depth. As a quick explanation, what you need an algorithm that does this:
For each subtree in the file tree that's more than one item deep:
For each leaf of the subtree:
$leaf[a][b][c] ... [y][z] -> $result[z][a][b][c] ... [y]
Here's some code that actually works.
function sane_file_array($files) {
$result = array();
$name = array();
$type = array();
$tmp_name = array();
$error = array();
$size = array();
foreach($files as $field => $data) {
foreach($data as $key => $val) {
$result[$field] = array();
if(!is_array($val)) {
$result[$field] = $data;
} else {
$res = array();
files_flip($res, array(), $data);
$result[$field] += $res;
}
}
}
return $result;
}
function array_merge_recursive2($paArray1, $paArray2) {
if (!is_array($paArray1) or !is_array($paArray2)) { return $paArray2; }
foreach ($paArray2 AS $sKey2 => $sValue2) {
$paArray1[$sKey2] = array_merge_recursive2(@$paArray1[$sKey2], $sValue2);
}
return $paArray1;
}
function files_flip(&$result, $keys, $value) {
if(is_array($value)) {
foreach($value as $k => $v) {
$newkeys = $keys;
array_push($newkeys, $k);
files_flip($result, $newkeys, $v);
}
} else {
$res = $value;
// Move the innermost key to the outer spot
$first = array_shift($keys);
array_push($keys, $first);
foreach(array_reverse($keys) as $k) {
// You might think we'd say $res[$k] = $res, but $res starts out not as an array
$res = array($k => $res);
}
$result = array_merge_recursive2($result, $res);
}
}
Just call sane_files_array on $_FILES and you should be good to go, regardless of how deep the $_FILES array is. This really should be part of the language itself, because the formatting of the $_FILES array is absolutely ridiculous.
Maybe:
Main photo: <input type="file" name="image1" />
Side photo 1: <input type="file" name="image2" />
Side photo 2: <input type="file" name="image3" />
Side photo 3: <input type="file" name="image4" />
$i=1;
while (isset($_FILES['image'.$i])) {
print_r($_FILES['image'.$i]);
$i++;
}
If you have to loop through specific file fields.
PHP's choice of how to handle $_FILES wastes a lot of developer time. Based on @Lendrick's answer, here is a similar OO approach.
/**
* @brief get the POSTed files in a more usable format
Works on the following methods:
<form method="post" action="/" name="" enctype="multipart/form-data">
<input type="file" name="photo1" />
<input type="file" name="photo2[]" />
<input type="file" name="photo2[]" />
<input type="file" name="photo3[]" multiple />
* @return Array
* @todo
* @see http://stackoverflow.com/questions/5444827/how-do-you-loop-through-files-array
*/
public static function GetPostedFiles()
{
/* group the information together like this example
Array
(
[attachments] => Array
(
[0] => Array
(
[name] => car.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpe1fdEB
[error] => 0
[size] => 2345276
)
)
[jimmy] => Array
(
[0] => Array
(
[name] => 1.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpx1HXrr
[error] => 0
[size] => 221041
)
[1] => Array
(
[name] => 2 ' .jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpQ1clPh
[error] => 0
[size] => 47634
)
)
)
*/
$Result = array();
$Name = array();
$Type = array();
$TmpName = array();
$Error = array();
$Size = array();
foreach($_FILES as $Field => $Data)
{
foreach($Data as $Key => $Val)
{
$Result[$Field] = array();
if(!is_array($Val))
$Result[$Field] = $Data;
else
{
$Res = array();
self::GPF_FilesFlip($Res, array(), $Data);
$Result[$Field] += $Res;
}
}
}
return $Result;
}
private static function GPF_ArrayMergeRecursive($PaArray1, $PaArray2)
{
// helper method for GetPostedFiles
if (!is_array($PaArray1) or !is_array($PaArray2))
return $PaArray2;
foreach ($PaArray2 AS $SKey2 => $SValue2)
$PaArray1[$SKey2] = self::GPF_ArrayMergeRecursive(@$PaArray1[$SKey2], $SValue2);
return $PaArray1;
}
private static function GPF_FilesFlip(&$Result, $Keys, $Value)
{
// helper method for GetPostedFiles
if(is_array($Value))
{
foreach($Value as $K => $V)
{
$NewKeys = $Keys;
array_push($NewKeys, $K);
self::GPF_FilesFlip($Result, $NewKeys, $V);
}
}
else
{
$Res = $Value;
// move the innermost key to the outer spot
$First = array_shift($Keys);
array_push($Keys, $First);
foreach(array_reverse($Keys) as $K)
$Res = array($K => $Res); // you might think we'd say $Res[$K] = $Res, but $Res starts out not as an array
$Result = self::GPF_ArrayMergeRecursive($Result, $Res);
}
}
I'm way late to the game for this answer but I got tired of solving the problem of dealing with the PHP files array over and over, so I wrote a composer package so I'd never have to again. Maybe someone googling will find my answer and be happy.
Install tvanc/files-array-organizer
composer require tvanc/files-array-organizer
Pass $_FILES
to it, and it'll give you back an array that's structured the way you were expecting.
<?php
use tvanc\FilesArrayOrganizer\FilesArrayOrganizer;
require 'vendor/autoload.php';
if ($_FILES) {
$organizedFiles = FilesArrayOrganizer::organize($_FILES);
// Now you can foreach over your files
foreach($organizedFiles['image'] as $file){
echo $file['name'];
}
}
?>
Main photo: <input type="file" name="image[]" />
Side photo 1: <input type="file" name="image[]" />
Side photo 2: <input type="file" name="image[]" />
Side photo 3: <input type="file" name="image[]" />
I like the attempt by @Lendrick to reconstruct the array. I totally agree that the original $_FILES array is purely insane in PHP.
Alternative 1
I came up with this function that supports multidimensional arrays such as <input type="file" name="a[b][c]" />
/*
* Return a sane list of uploaded files
* @author tim-international.net
*/
function get_uploaded_files() {
$result = [];
foreach (preg_split('#&#', http_build_query($_FILES, '&'), -1, PREG_SPLIT_NO_EMPTY) as $pair) {
list($key, $value) = explode('=', $pair);
$key = urlencode(preg_replace('#^([^\[]+)\[(name|tmp_name|type|size|error)\](.*)$#', '$1$3[$2]', urldecode($key)));
$result[] = $key .'='. $value;
}
parse_str(implode('&', $result), $result);
return $result;
}
Example output for <input type="file" name="image[]" multiple />
:
array(1) {
["image"]=>
array(1) {
[0]=>
array(5) {
["name"]=>
string(20) "My uploaded file1.png"
["type"]=>
string(9) "image/png"
["tmp_name"]=>
string(27) "C:\Windows\Temp\php6A8E1.tmp"
["error"]=>
int(0)
["size"]=>
int(26570)
}
[1]=>
array(5) {
["name"]=>
string(20) "My uploaded file2.png"
["type"]=>
string(9) "image/png"
["tmp_name"]=>
string(27) "C:\Windows\Temp\php6A8E2.tmp"
["error"]=>
int(0)
["size"]=>
int(26570)
}
}
}
Example of use:
$uploaded = get_uploaded_files();
foreach ($uploaded['image'] as $i => $file) {
move_uploaded_file($uploaded[$i]['tmp_name'], ...);
}
Alternative 2
Another variant which gives a more flattened array that can be handy is this:
function get_uploaded_files() {
$result = [];
foreach (preg_split('#&#', http_build_query($_FILES, '&'), -1, PREG_SPLIT_NO_EMPTY) as $pair) {
list($key, $value) = explode('=', $pair);
if (preg_match('#^([^\[]+)\[(name|tmp_name|type|size|error)\](.*)$#', urldecode($key), $matches)) {
$result[$matches[1].$matches[3]][$matches[2]] = urldecode($value);
}
}
return $result;
}
Which returns the following for <input type="file" name="foo[bar][]" multiple />
:
array(1) {
["foo[bar][0]"]=>
array(5) {
["name"]=>
string(20) "My uploaded file1.png"
["type"]=>
string(9) "image/png"
["tmp_name"]=>
string(27) "C:\Windows\Temp\php6A8E1.tmp"
["error"]=>
int(0)
["size"]=>
int(26570)
}
["foo[bar][1]"]=>
array(5) {
["name"]=>
string(20) "My uploaded file2.png"
["type"]=>
string(9) "image/png"
["tmp_name"]=>
string(27) "C:\Windows\Temp\php6A8E2.tmp"
["error"]=>
int(0)
["size"]=>
int(26570)
}
}
}
Example of use:
foreach (get_uploaded_files() as $field => $file) {
move_uploaded_file($file['tmp_name'], ...);
}
I have struggled with this dilemma for almost a week! Nothing I found on the net could help me. I knew sort of what to do, but could not figure out how to loop through the $_FILES array properly - until now when I read the edited post of the accepted answer.
I made some changes though, in the script as posted, as it did not work properly for me. I wanted to be able to determine if a file was selected at all, so I changed the line "if( !empty( $_FILES[ 'image' ][ 'error' ][ $index ] ) )" to "if( !empty( $_FILES[ 'image' ][ 'size' ][ $index ] ) )" and then instead of "return false;", I put the size into a variable instead: "$Size = $_FILES[ 'upload' ][ 'size' ][ $index ];"
This way I could check if the $Size variable was larger than zero. If it was, then a file had been selected and I could continue with counting the number of files and do the actual upload. I did not use any of the "unnecessary" script after "return false;", in the accepted answer. Hope this helps someone.
: P /MacD
If you can't join them beat them.
https://gist.github.com/noorwachid/fce70a3a2d96502c2805248c46fc23f9
<?php
class UploadedFile
{
public static function walk(&$originalPath, &$path, &$contentType, &$size, &$errorCode)
{
if (is_array($originalPath)) {
foreach ($originalPath as $key => &$value)
self::walk($value, $path[$key], $contentType[$key], $size[$key], $errorCode[$key]);
} else {
$originalPath = [
'originalPath' => $originalPath,
'path' => $path,
'contentType' => $contentType,
'size' => $size,
'errorCode' => $errorCode,
];
}
}
public static function build()
{
// swap second keys
$rootNode = [];
foreach ($_FILES as $key => $value) {
foreach ($value as $key2 => $value2) {
$rootNode[$key2][$key] = $value2;
}
}
// swap first and last keys
self::walk($rootNode['name'], $rootNode['tmp_name'], $rootNode['type'], $rootNode['size'], $rootNode['error']);
// remove unused keys
unset($rootNode['tmp_name']);
unset($rootNode['type']);
unset($rootNode['size']);
unset($rootNode['error']);
return $rootNode['name'];
}
}
// original
print_r($_FILES);
// restructured
print_r(UploadedFile::build());
// original
Array
(
[one] => Array
(
[name] => ss_break.png
[type] => image/png
[tmp_name] => /tmp/phppY8lSV
[error] => 0
[size] => 43582
)
[multiple] => Array
(
[name] => Array
(
[0] => LeleBreeder.png
)
[type] => Array
(
[0] => image/png
)
[tmp_name] => Array
(
[0] => /tmp/php6GSj1X
)
[error] => Array
(
[0] => 0
)
[size] => Array
(
[0] => 14284
)
)
[crazy] => Array
(
[name] => Array
(
[nested] => Array
(
[keys] => PlainUbuntu.png
)
)
[type] => Array
(
[nested] => Array
(
[keys] => image/png
)
)
[tmp_name] => Array
(
[nested] => Array
(
[keys] => /tmp/php4boHkj
)
)
[error] => Array
(
[nested] => Array
(
[keys] => 0
)
)
[size] => Array
(
[nested] => Array
(
[keys] => 25668
)
)
)
)
// restructured
Array
(
[one] => Array
(
[originalPath] => ss_break.png
[path] => /tmp/phppY8lSV
[contentType] => image/png
[size] => 43582
[errorCode] => 0
)
[multiple] => Array
(
[0] => Array
(
[originalPath] => LeleBreeder.png
[path] => /tmp/php6GSj1X
[contentType] => image/png
[size] => 14284
[errorCode] => 0
)
)
[crazy] => Array
(
[nested] => Array
(
[keys] => Array
(
[originalPath] => PlainUbuntu.png
[path] => /tmp/php4boHkj
[contentType] => image/png
[size] => 25668
[errorCode] => 0
)
)
)
)
精彩评论