PHP Upload Multiple Files

Summary: in this tutorial, you’ll learn how to upload multiple files to the server in PHP securely.

Introduction to the PHP upload multiple files

In the previous tutorial, you learned how to upload a single file from a client to the webserver in PHP. All the rules of uploading a single file are relevant to multiple files.

First, the HTML form element must have the enctype attribute sets to "multipart/form-data" to enable file uploading. For example:

<form action="index.php" method="post" enctype="multipart/form-data">Code language: PHP (php)

Second, the file input element must have the multiple attribute and its name must have the square brackets ([]) like this:

<input type="file" name="files[]" id="files" multiple />Code language: PHP (php)

In PHP, you can access the $_FILES['files'] to get the information of the uploaded files:

<?php

var_dump($_FILES['files']);Code language: HTML, XML (xml)

The 'files' is the name of the file input element.

PHP upload multiple files example

The following example reuses the existing functions and logic developed in the file upload tutorial.

First, create the following project structure:

├── inc
|  ├── flash.php
|  └── functions.php
├── index.php
├── upload.php
└── uploads

Second, add the following code to the index.php to create the file upload form:

<?php
session_start();
require_once __DIR__ . '/inc/flash.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>PHP upload multiple files</title>
    <link rel="stylesheet" href="https://www.phptutorial.net/app/css/style.css" />
</head>
<body>
<?php flash('upload') ?>
<main>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        <div>
            <label for="files">Select files to upload:</label>
            <input type="file" name="files[]" id="files" multiple required/>
        </div>
        <div>
            <button type="submit">Upload</button>
        </div>
    </form>
</main>
</body>
</html>Code language: HTML, XML (xml)

The index.php does the following:

1) Start a new session or resume an existing session:

session_start();

2) Load the code from the inc/flash.php file:

require_once __DIR__ . '/inc/flash.php';Code language: PHP (php)

3) Call the flash() function to show a message with the name 'upload'. The flash() function is defined in the flash.php file.

<?php flash('upload') ?>Code language: HTML, XML (xml)

4) Create an upload form that submits to the upload.php file.

Third, add the following code to the upload.php file to validate and upload multiple files:

<?php

session_start();

require_once __DIR__ . '/inc/flash.php';
require_once __DIR__ . '/inc/functions.php';

const ALLOWED_FILES = [
    'image/png' => 'png',
    'image/jpeg' => 'jpg'
];

const MAX_SIZE = 5 * 1024 * 1024; //  5MB

const UPLOAD_DIR = __DIR__ . '/uploads';


$is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post';
$has_files = isset($_FILES['files']);

if (!$is_post_request || !$has_files) {
    redirect_with_message('Invalid file upload operation', FLASH_ERROR);
}

$files = $_FILES['files'];
$file_count = count($files['name']);

// validation
$errors = [];
for ($i = 0; $i < $file_count; $i++) {
    // get the uploaded file info
    $status = $files['error'][$i];
    $filename = $files['name'][$i];
    $tmp = $files['tmp_name'][$i];

    // an error occurs
    if ($status !== UPLOAD_ERR_OK) {
        $errors[$filename] = MESSAGES[$status];
        continue;
    }
    // validate the file size
    $filesize = filesize($tmp);

    if ($filesize > MAX_SIZE) {
        // construct an error message
        $message = sprintf("The file %s is %s which is greater than the allowed size %s",
            $filename,
            format_filesize($filesize),
            format_filesize(MAX_SIZE));

        $errors[$filesize] = $message;
        continue;
    }

    // validate the file type
    if (!in_array(get_mime_type($tmp), array_keys(ALLOWED_FILES))) {
        $errors[$filename] = "The file $filename is allowed to upload";
    }
}

if ($errors) {
    redirect_with_message(format_messages('The following errors occurred:',$errors), FLASH_ERROR);
}

// move the files
for($i = 0; $i < $file_count; $i++) {
    $filename = $files['name'][$i];
    $tmp = $files['tmp_name'][$i];
    $mime_type = get_mime_type($tmp);

    // set the filename as the basename + extension
    $uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type];
    // new filepath
    $filepath = UPLOAD_DIR . '/' . $uploaded_file;

    // move the file to the upload dir
    $success = move_uploaded_file($tmp, $filepath);
    if(!$success) {
        $errors[$filename] = "The file $filename was failed to move.";
    }
}

$errors ?
    redirect_with_message(format_messages('The following errors occurred:',$errors), FLASH_ERROR) :
    redirect_with_message('All the files were uploaded successfully.', FLASH_SUCCESS);
Code language: HTML, XML (xml)

How the upload.php works:

1) Start a new session or resume an existing one:

session_start();

2) Load the code from the flash.php and functions.php:

require_once __DIR__ . '/inc/flash.php';
require_once __DIR__ . '/inc/functions.php';Code language: PHP (php)

Note that you should use the flash.php from the flash message tutorial and functions.php from the file upload tutorial.

Since the upload.php deals with multiple files, it will issue multiple error messages, one for each uploaded file.

To make it convenient, we can define a function that returns a single error message from multiple error messages like this:

function format_messages(string $title, array $messages): string
{
    $message = "<p>$title</p>";
    $message .= '<ul>';
    foreach ($messages as $key => $value) {
        $message .= "<li>$value</li>";
    }
    $message .= '<ul>';

    return $message;
}Code language: PHP (php)

And add this format_messages() function to the functions.php file.

3) Return an error message if the request method is not POST or the $_FILES does not contain the files field:

$is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post';
$has_files = isset($_FILES['files']);

if (!$is_post_request || !$has_files) {
    redirect_with_message('Invalid file upload operation', FLASH_ERROR);
}
Code language: PHP (php)

4) Get the uploaded files from $_FILES and the number of files uploaded:

$files = $_FILES['files'];
$file_count = count($files['name']);Code language: PHP (php)

5) For each uploaded file, check the error code and validate the file size, type. If an error occurred, add an error message to the $errors array, skip the current validation loop, and validate the next file.

// validation
$errors = [];
for ($i = 0; $i < $file_count; $i++) {
    // get the uploaded file info
    $status = $files['error'][$i];
    $filename = $files['name'][$i];
    $tmp = $files['tmp_name'][$i];

    // an error occurs
    if ($status !== UPLOAD_ERR_OK) {
        $errors[$filename] = MESSAGES[$status];
        continue;
    }
    // validate the file size
    $filesize = filesize($tmp);

    if ($filesize > MAX_SIZE) {
        // construct an error message
        $message = sprintf("The file %s is %s which is greater than the allowed size %s",
            $filename,
            format_filesize($filesize),
            format_filesize(MAX_SIZE));

        $errors[$filesize] = $message;
        continue;
    }

    // validate the file type
    if (!in_array(get_mime_type($tmp), array_keys(ALLOWED_FILES))) {
        $errors[$filename] = "The file $filename is allowed to upload";
    }
}Code language: PHP (php)

If an error occurred, redirect back to the index.php and show the error message:

if ($errors) {
    redirect_with_message(format_messages('The following errors occurred:',$errors), FLASH_ERROR);
}
Code language: PHP (php)

Note that we use the format_messages() function to convert the $errors array to a single formatted error message.

6) If no error occurs, move each file to the upload directory:

// move the files
for($i = 0; $i < $file_count; $i++) {
    $filename = $files['name'][$i];
    $tmp = $files['tmp_name'][$i];
    $mime_type = get_mime_type($tmp);

    // set the filename as the basename + extension
    $uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type];
    // new filepath
    $filepath = UPLOAD_DIR . '/' . $uploaded_file;

    // move the file to the upload dir
    $success = move_uploaded_file($tmp, $filepath);
    if(!$success) {
        $errors[$filename] = "The file $filename was failed to move.";
    }
}Code language: PHP (php)

8) Show the error message if the move has an error. Otherwise, show a success message:

$errors ?
    redirect_with_message(format_messages('The following errors occurred:',$errors), FLASH_ERROR) :
    redirect_with_message('All the files were uploaded successfully.', FLASH_SUCCESS);
Code language: PHP (php)

Summary

  • Set enctype attribute of the form to "multipart/form-data" to enable file uploading in the form.
  • Set the multiple attribute and add square brackets ([]) to the file input element to upload multiple files.
  • Always validate the information in the $_FILES before processing them.
  • Use the move_uploaded_file() function to move the upload files.
Did you find this tutorial useful?