How do you loop through $_FILES array?

66,287

Solution 1

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.

Solution 2

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']; 
}

Solution 3

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;
}

Solution 4

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.

Solution 5

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.

Share:
66,287

Related videos on Youtube

Ben
Author by

Ben

Updated on July 09, 2022

Comments

  • Ben
    Ben almost 2 years

    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 itself, 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.

    • Matthew
      Matthew about 13 years
      Have you tried var_dump($_FILES)? A little debugging goes a long way to figuring out what PHP is doing.
  • Ben
    Ben about 13 years
    how come count($_FILES) doesnt work, when the field is empty, it turns out 1. and there is noway you can loop through files input with image[]???
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    : OK, but what caused the described behaviour?
  • Your Common Sense
    Your Common Sense about 13 years
    @Tomalak the way array being populated. just print_r($_FILES) in both ways and see.
  • Your Common Sense
    Your Common Sense about 13 years
    @Ben actually you can, but just another way. count($_FILES) wouldn't work, you have to check error elements instead
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    : I'm saying that your answer should explain it.
  • Maarten
    Maarten over 2 years
    Thanks! I was able to use your function to create another loop to save all the posted files and put them in our custom framework :)
  • FolioGraphic
    FolioGraphic about 2 years
    Not counting the versions below that do the same as this, I found this to be the most helpful response. This actually turned the $_FILES into exactly what would be expected and you can then loop through them the way you would think you should.

Related