Development

Laravel 9 image upload and validation example

How to do an image upload form and handler in Laravel 9 with validation.

Included is a controller, upload form and migration for the database table to store information about the image upload.

The controller

Create the image upload controller with

php artisan make:controller ImageUploadController

This will handle showing the upload form blade file and the processing of the image upload (validation, renaming and storing).

Inside app/Http/Controllers/ImageUploadController.php paste the following:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;


class ImageUploadController extends Controller

{
    public function index()
    {
        return view('images.upload');
    }

    public function store(Request $request)
    {
        $request->validate([
            'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048'
        ]);

        $original_name = $request->image->getClientOriginalName();//Original image file name

        $file_size = $request->image->getSize();//Size in bytes

        $extension = $request->image->extension();//Image file extension eg. jpg or png

        $directory = 'uploads/' . date('Y') . '/' . date('m');//Upload and move image into this directory

        $id = Str::random(8);//Random 8 character string

        $image_name = $id . '.' . $extension;

        $full_save_path_name = $directory . '/' . $image_name;

        $request->image->move(public_path($directory), $image_name);//Save into: public/images

        DB::table('images')->insert([
            'id' => $id,
            'user_id' => Auth::id(),
            'size' => $file_size,
            'original_name' => $original_name,
            'directory' => $directory,
            'extension' => $extension,
            'uploaded' => date('Y-m-d H:i:s')
        ]);

        return back()
            ->with("success", "You have successfully uploaded $original_name saved as: $image_name")
            ->with("image", $full_save_path_name);
    }

}

The uploaded image will be validated with 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048' this means the form field is required, it must be an image in jpg/png/gif/svg format and the maximum allowed size is 2048 kilobytes.

The save directory will be public/uploads/YEAR/MONTH e.g public/uploads/2022/04

The image will be renamed as a randomly generated id, 8 characters long.

The routes

In routes/web.php add:

Route::controller(ImageUploadController::class)->middleware('auth')->group(function(){

    Route::get('image-upload', 'index');

    Route::post('image-upload', 'store')->name('image.store');

});

These two routes are protected by ‘auth’ meaning only logged in users can view them. The two routes are /image-upload as a GET which calls index and /image-upload as a POST which calls the store method from ImageUploadController.

Run php artisan route:cache to refresh the routes.

The upload form page

This will be the form upload page as a blade file, put in resources/views/images the file upload.blade.php with the following:

<!DOCTYPE html>
<html>
<head>
    <title>Upload image</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="card">
        <div class="card-body">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h2>Upload image</h2>
                </div>
                <div class="panel-body">
                    @if ($message = Session::get('success'))
                        <div class="alert alert-success alert-block">
                            <strong>{{ $message }}</strong>
                        </div>
                        <img class="img-fluid" src="{{ Session::get('image') }}" alt="uploaded image">
                    @endif
                    <form action="{{ route('image.store') }}" method="POST" enctype="multipart/form-data">
                        @csrf
                        <div class="mb-3">
                            <label class="form-label" for="image_input">Image:</label>
                            <input type="file" name="image" id="image_input"
                                   class="form-control @error('image') is-invalid @enderror">
                            @error('image')
                            <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                        <div class="mb-3">
                            <button type="submit" class="btn btn-success">Upload</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

Normally this blade file will be an extension of the main layout however for this example it encompasses the whole page with the header and CSS source.

The migration

This is if you want to insert details about the image upload into the database.

If you do not want to do this skip this section and delete the DB::table('images')->insert... section of the ImageUploadController store method.

Run: php artisan make:migration images

Find the database/migrations create_images_table.php file and paste:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up()
    {
        Schema::create('images', function (Blueprint $table) {
            $table->char('id', 8);
            $table->bigInteger('user_id');
            $table->integer('size');
            $table->string('original_name');
            $table->string('directory');
            $table->string('extension');
            $table->date('uploaded');
            $table->primary(['id']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('images');
    }
};

Run migrate to create this table: php artisan migrate

Usage

Once logged in navigate to /image-upload

If you upload a non-image you get this notice

or if the image is oversized (greater than 2048 Kilobytes)

Upon a successful upload:

 

Share

Recent Posts

Kennington reservoir drained drone images

A drained and empty Kennington reservoir images from a drone in early July 2024. The…

1 year ago

Merrimu Reservoir drone images

Merrimu Reservoir from drone. Click images to view larger.

1 year ago

FTP getting array of file details such as size using PHP

Using FTP and PHP to get an array of file details such as size and…

2 years ago

Creating Laravel form requests

Creating and using Laravel form requests to create cleaner code, separation and reusability for your…

2 years ago

Improving the default Laravel login and register views

Improving the default Laravel login and register views in such a simple manner but making…

2 years ago

Laravel validation for checking if value exists in the database

Laravel validation for checking if a field value exists in the database. The validation rule…

2 years ago