Image Upload in PHP: A Step-by-Step Guide

Handling image uploads is a common task in web development. This guide will walk you through setting up image uploads in pure PHP, utilizing Composer for dependency management and ensuring robust validation.

Prerequisites

Before we start, ensure you have PHP and Composer installed on your system. If not, you can download and install them from their respective official websites.

Step 1: Set Up the Project

  1. Create a project directory:
        mkdir php-image-upload
        cd php-image-upload


  2. Initialize a Composer project:
    composer init

Follow the prompts to set up your Composer project.

  1. Install necessary dependencies:

For this example, we'll use the vlucas/phpdotenv library to manage environment variables:

    composer require vlucas/phpdotenv


Step 2: Configure Environment Variables
Create a .env file in the project root to store environment variables:

UPLOAD_DIR=uploads
MAX_FILE_SIZE=2097152 # 2MB in bytes
ALLOWED_TYPES=image/jpeg,image/png,image/gif


Step 3: Create the Directory Structure
Set up the necessary directories and files:

mkdir -p public/uploads
touch public/index.php
touch public/upload.php


Step 4: Create a Helper Class for Handling Uploads
Create a UploadHandler.php file in the src directory:

<?php

namespace App;

class UploadHandler
{
    protected $uploadDir;
    protected $maxFileSize;
    protected $allowedTypes;

    public function __construct($uploadDir, $maxFileSize, $allowedTypes)
    {
        $this->uploadDir = $uploadDir;
        $this->maxFileSize = $maxFileSize;
        $this->allowedTypes = explode(',', $allowedTypes);
    }

    public function validate($file)
    {
        // Check if file was uploaded without errors
        if ($file['error'] !== UPLOAD_ERR_OK) {
            throw new \Exception('File upload error.');
        }

        // Check file size
        if ($file['size'] > $this->maxFileSize) {
            throw new \Exception('File is too large.');
        }

        // Check file type
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $fileType = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);

        if (!in_array($fileType, $this->allowedTypes)) {
            throw new \Exception('Invalid file type.');
        }

        return true;
    }

    public function upload($file)
    {
        $this->validate($file);

        $fileName = uniqid() . '-' . basename($file['name']);
        $destination = $this->uploadDir . '/' . $fileName;

        if (!move_uploaded_file($file['tmp_name'], $destination)) {
            throw new \Exception('Failed to move uploaded file.');
        }

        return $fileName;
    }
}


Step 5: Set Up Environment Variables Loading
In the public/index.php file, set up environment variables loading:

<?php

require __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use App\UploadHandler;

$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();

$uploadDir = $_ENV['UPLOAD_DIR'];
$maxFileSize = $_ENV['MAX_FILE_SIZE'];
$allowedTypes = $_ENV['ALLOWED_TYPES'];

$handler = new UploadHandler($uploadDir, $maxFileSize, $allowedTypes);


Step 6: Create the Upload Form
In the public/index.php file, add the HTML for the upload form:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Upload</title>
</head>
<body>
    <h1>Upload File</h1>
    <?php if (!empty($_GET['success'])): ?>
        <div>
            File uploaded successfully!
            <img src="uploads/<?php echo htmlspecialchars($_GET['file']); ?>" alt="Uploaded Image">
        </div>
    <?php endif; ?>
    <?php if (!empty($_GET['error'])): ?>
        <div>
            Error: <?php echo htmlspecialchars($_GET['error']); ?>
        </div>
    <?php endif; ?>
    <form action="upload.php" method="POST" enctype="multipart/form-data">
        <label for="file">Choose a file:</label>
        <input type="file" name="file" id="file" required>
        <button type="submit">Upload</button>
    </form>
</body>
</html>


Step 7: Handle the Upload Logic
In the public/upload.php file, handle the file upload logic:

<?php

require __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use App\UploadHandler;

$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();

$uploadDir = $_ENV['UPLOAD_DIR'];
$maxFileSize = $_ENV['MAX_FILE_SIZE'];
$allowedTypes = $_ENV['ALLOWED_TYPES'];

$handler = new UploadHandler($uploadDir, $maxFileSize, $allowedTypes);

try {
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
        $fileName = $handler->upload($_FILES['file']);
        header('Location: index.php?success=1&file=' . urlencode($fileName));
        exit;
    }
} catch (Exception $e) {
    header('Location: index.php?error=' . urlencode($e->getMessage()));
    exit;
}

header('Location: index.php');


Step 8: Ensure Permissions
Ensure that the public/uploads directory is writable by the web server:

chmod -R 775 public/uploads


Conclusion
By following this guide, you have set up a robust file upload system in pure PHP using Composer for dependency management. This setup includes environment variable management with phpdotenv, file validation, and proper error handling. Feel free to customize the validation, error messages, and other configurations to suit your specific requirements.

Also Read:
Image Upload in Laravel: A Comprehensive Guide