Back to Blog
Lesson 42 of the PHP: Modern PHP from the Ground Up course
PHPAugust 30, 20263 min read

Handling File Uploads in PHP: A Beginner’s Guide

Learn to process file uploads in PHP securely. We cover the $_FILES superglobal, file type validation, and storing files safely in your MVC application.

PHPfile uploadssecurityformsdevelopment
From below of monitor of modern computer with opened files on blue screen

Previously in this course, we explored Working with JSON APIs to handle data interchange. This lesson adds the ability to accept binary files from users, a common requirement for profile pictures, documents, or media assets.

The $_FILES Superglobal

When a user submits a file via an HTML form, PHP doesn't put the data in $_POST. Instead, it populates the $_FILES superglobal array.

To enable this, your HTML form must include the attribute enctype="multipart/form-data". Without this, the browser sends the filename as a text string rather than the actual file content.

HTML
style="color:#808080"><style="color:#4EC9B0">form action="/upload" method="POST" enctype="multipart/form-data">
    style="color:#808080"><style="color:#4EC9B0">input type="file" name="avatar">
    style="color:#808080"><style="color:#4EC9B0">button type="submit">Uploadstyle="color:#808080"></style="color:#4EC9B0">button>
style="color:#808080"></style="color:#4EC9B0">form>

When submitted, $_FILES['avatar'] contains an associative array with five keys:

  • name: The original name of the file on the user's machine.
  • type: The MIME type provided by the browser (e.g., image/jpeg). Do not trust this.
  • tmp_name: The path to the temporary file stored on the server.
  • error: An integer representing the upload status (0 means success).
  • size: The file size in bytes.

Securely Validating File Uploads

Never trust user input. A malicious user can easily spoof the type reported by the browser. To handle file uploads securely, you must perform server-side checks.

  1. Check for Upload Errors: Ensure error === UPLOAD_ERR_OK.
  2. Validate File Type: Use finfo (File Information) to inspect the binary content of the file, not just the extension.
  3. Limit Size: Prevent disk exhaustion by setting a maximum file size.
  4. Sanitize Filenames: Never use the user-provided name directly. It could contain directory traversal characters like ../.

Worked Example: Moving Uploaded Files

In our MVC project, we want to store user uploads in a storage/uploads directory. Here is how you process the request in a controller:

PHP
public function storeAvatar()
{
    $file = $_FILES['avatar'];

    #6A9955">// 1. Basic validation
    if ($file['error'] !== UPLOAD_ERR_OK) {
        throw new Exception("Upload failed with error code " . $file['error']);
    }

    #6A9955">// 2. Validate MIME type using finfo
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);
    $allowedTypes = ['image/jpeg', 'image/png'];

    if (!in_array($mimeType, $allowedTypes)) {
        throw new Exception("Invalid file type.");
    }

    #6A9955">// 3. Generate a secure, unique filename
    $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
    $newFilename = bin2hex(random_bytes(16)) . '.' . $extension;
    $destination = __DIR__ . '/../../storage/uploads/' . $newFilename;

    #6A9955">// 4. Move to permanent storage
    if (move_uploaded_file($file['tmp_name'], $destination)) {
        echo "File saved successfully as " . $newFilename;
    } else {
        throw new Exception("Failed to move uploaded file.");
    }
}

Hands-on Exercise

  1. Create a storage/uploads folder in your project root and ensure it is writable by the web server.
  2. Update your controller to handle a file upload.
  3. Test it by uploading a small image and verifying it appears in your storage directory with a randomized name.

Common Pitfalls

  • Trusting $_FILES['type']: Always use finfo to verify the actual content. A user can rename a malicious .php script to .jpg and trick the server if you only check extensions.
  • Overwriting files: Always generate a unique filename (e.g., random_bytes or uniqid) to prevent users from overwriting existing files.
  • Permissions: Ensure your storage/uploads directory is not directly executable by the web server. You can achieve this via .htaccess or by storing files outside the public/ web root.

FAQ

Q: How do I handle large file uploads? A: PHP has upload_max_filesize and post_max_size settings in php.ini. For very large files, consider using cloud storage services like S3, as discussed in Handling Large File Uploads: Streaming to S3 & Async Processing.

Q: Is move_uploaded_file safe? A: Yes, it is designed specifically for this purpose. It verifies that the file being moved is indeed a valid uploaded file, preventing attackers from moving arbitrary files from your server's system directories.

Recap

Handling file uploads requires validating the file's contents, generating unique filenames to prevent collisions, and moving files from temporary storage to a secure location. By combining finfo for type checking and move_uploaded_file for transfer, you ensure your forms remain a secure entry point for user content.

Up next: We will explore performance optimization basics, including output buffering and query caching to make your application faster.

Similar Posts