Backend Development

Handling Directory Uploads in PHP 8 with $_FILES and the webkitdirectory Attribute

Handling Directory Uploads in PHP 8 with $_FILES and the webkitdirectory Attribute

Uploading multiple files in one go has always been possible in PHP through the multiple attribute on <input type="file">. But what if you want users to upload an entire directory structure (folders with nested files)?

 

 

 

Modern browsers like Chrome and Edge support the webkitdirectory attribute, which allows users to select a folder and upload all its contents—including subfolders—in one request.

In this post, I’ll walk you through how to use webkitdirectory on the frontend and handle uploaded directories in PHP 8.1 using $_FILES.

 

The webkitdirectory attribute can be combined with multiple to allow folder uploads:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Directory Upload Example</title>
</head>
<body>
  <h2>Upload a Folder</h2>
  <form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="files[]" webkitdirectory multiple />
    <button type="submit">Upload Folder</button>
  </form>
</body>
</html>

Once the webkitdirectory is added,  it allows to select a whole directory to upload. Then browser submits all files inside that directory, including all sub directories and their files, to the server. 

How Files Appear in $_FILES

When the form is submitted, PHP populates $_FILES['files']. You will see a new key is added in $_FILES which is full_path. This key wasn’t available prior to PHP 8.1. 
 
The full_path stores all the file paths inside the selected directory and the sub-directories like so:
array(1) {
  ["files"]=>
  array(6) {
    ["name"]=>
    array(6) {
      [0]=>
      string(50) "448817007_314500681734008_586613966563609032_n.jpg"
      [1]=>
      string(52) "448802325_1003140208055949_7606942409710826856_n.jpg"
      [2]=>
      string(52) "448812451_1816931272150449_7551978461458049653_n.jpg"
      [3]=>
      string(12) "IMG_2772.jpg"
      [4]=>
      string(12) "IMG_2773.jpg"
      [5]=>
      string(12) "IMG_2774.jpg"
    }
    ["full_path"]=>
    array(6) {
      [0]=>
      string(55) "data/448817007_314500681734008_586613966563609032_n.jpg"
      [1]=>
      string(57) "data/448802325_1003140208055949_7606942409710826856_n.jpg"
      [2]=>
      string(57) "data/448812451_1816931272150449_7551978461458049653_n.jpg"
      [3]=>
      string(24) "data/photos/IMG_2772.jpg"
      [4]=>
      string(24) "data/photos/IMG_2773.jpg"
      [5]=>
      string(24) "data/photos/IMG_2774.jpg"
    }
    ["type"]=>
    array(6) {
      [0]=>
      string(10) "image/jpeg"
      [1]=>
      string(10) "image/jpeg"
      [2]=>
      string(10) "image/jpeg"
      [3]=>
      string(10) "image/jpeg"
      [4]=>
      string(10) "image/jpeg"
      [5]=>
      string(10) "image/jpeg"
    }

  ...
  ...

Notice how $_FILES['files']['full_path'] preserves the relative path of each file inside the selected folder. With the knowledge of the full_path array we can upload all the files in the directory to the server.

 

Handling Uploads in PHP

Here’s an example upload.php script to save files while maintaining their folder hierarchy:

<?php
$uploadDir = __DIR__ . "/uploads/";

// Ensure the base upload directory exists
if (!is_dir($uploadDir)) {
    mkdir($uploadDir, 0777, true);
}

foreach ($_FILES as $name => $item) {

    // Create the directory
    $dirPath = $uploadDir . $name;
    if (!is_dir($dirPath)) {
        mkdir($dirPath, 0777, true);
    }

    foreach ($item['name'] as $index => $filename) {
        $tmpName = $item['tmp_name'][$index];
        $error   = $item['error'][$index];
        $fullPath   = $item['full_path'][$index];

        if ($error === UPLOAD_ERR_OK) {
            $path = str_replace(['..', '\\'], '', $fullPath);

            $destination = $dirPath .DIRECTORY_SEPARATOR . $path;

            $subDir = dirname($destination);
            if (!is_dir($subDir)) {
                mkdir($subDir, 0777, true);
            }

            // Move uploaded file
            move_uploaded_file($tmpName, $destination);
        }
    }
}

The code is straightforward, in the outer foreach we loop into the $_FILES superglobals in case there are multiple directories. To maintain the folder hierarchy, we create the main directory to upload the files in:

$dirPath = $uploadDir . $name;
    if (!is_dir($dirPath)) {
        mkdir($dirPath, 0777, true);
    }

Then we iterate over the files in each directory using another foreach. In the inner foreach we retrieve each file full_path, tmp_name, error:

foreach ($item['name'] as $index => $filename) {
$tmpName = $item['tmp_name'][$index];
        $error   = $item['error'][$index];
        $fullPath   = $item['full_path'][$index];

        if ($error === UPLOAD_ERR_OK) {

         }

}

We construct the destination path of each file using the key full_path. The full_path key stores the full path of each file, even if it exists in nested folders.

$destination = $dirPath .DIRECTORY_SEPARATOR . $path;

            $subDir = dirname($destination);
            if (!is_dir($subDir)) {
                mkdir($subDir, 0777, true);
            }

And the final step is to move the uploaded file using php move_upload_file() function.

 

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments