Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

File uploads

An avatar, a product photo, a document — users upload files all the time. PHP receives the file itself and puts it into a temporary folder; your job is to check it and move it to a permanent place.

The examples in this lesson need a web server, so they cannot be run here.

The form

For a form to send files it needs the post method and the enctype attribute:

<form method="post" action="/upload.php" enctype="multipart/form-data">
  <input type="file" name="avatar">
  <button type="submit">Upload</button>
</form>

Without enctype="multipart/form-data" only the file name reaches the server.

The $_FILES array

Information about the uploaded file is in $_FILES under the field name:

<?php
 
$file = $_FILES['avatar'];
 
echo $file['name'];     // photo.jpg — the name on the user's computer
echo $file['size'];     // the size in bytes
echo $file['tmp_name']; // the path to the temporary file on the server
echo $file['error'];    // the error code, 0 means everything is fine

Checking for an error

The first thing to check is that the file really was uploaded:

<?php
 
$file = $_FILES['avatar'] ?? null;
 
if ($file === null || $file['error'] !== UPLOAD_ERR_OK) {
    exit('No file was uploaded');
}

An error occurs if no file was chosen or it exceeds the upload_max_filesize limit from the server settings.

Checking the size and type

The file name and the type sent by the browser cannot be trusted — they are easy to forge. The real type is determined from the content:

<?php
 
$file = $_FILES['avatar'];
 
if ($file['size'] > 2 * 1024 * 1024) {
    exit('The file is larger than 2 MB');
}
 
$type = mime_content_type($file['tmp_name']);
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
 
if (!isset($allowed[$type])) {
    exit('Only images are allowed');
}

Saving

The temporary file disappears when the request ends. The move_uploaded_file() function moves it to a permanent folder. The name is one you make up — random, with the extension taken from the verified type:

<?php
 
$file = $_FILES['avatar'];
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
$extension = $allowed[mime_content_type($file['tmp_name'])];
 
$name = bin2hex(random_bytes(16)) . '.' . $extension;
$target = __DIR__ . '/uploads/' . $name;
 
if (move_uploaded_file($file['tmp_name'], $target)) {
    echo 'Saved: ' . $name;
}

A random name solves two problems at once: files from different users do not overwrite each other, and someone else's file name never gets into the path.

Several files

Give the field a name with square brackets and the multiple attribute. Then every element of $_FILES becomes an array:

<?php
 
foreach ($_FILES['photos']['tmp_name'] as $index => $tmpName) {
    if ($_FILES['photos']['error'][$index] === UPLOAD_ERR_OK) {
        echo $_FILES['photos']['name'][$index];
    }
}

Common mistake

Never save a file under the name the user sent. A shell.php file in a folder reachable from the browser becomes someone else's code on your server, and a name like ../../index.php overwrites the site's files. Check the content, assign your own name and keep uploads in a folder where the server does not execute PHP.

Thanks for reading!

Donate to the site — we work hard for you.