Create Custom Laravel Validation Rule for Total Uploaded Files Size 🚫✅

Mostafa Said - Jul 10 '22 - - Dev Community

I'm currently working on a major side project that takes up practically all of my free time. In this project, I had to check an array of submitted files to ensure that the total size was less than 10mb. I couldn't find anything in Laravel's validation docs that could accomplish that for me, so I decided I'd have to write my own custom validation rule.

I was looking for the best possible way to validate array of uploaded files, and I found big part of the solution on StackOverFlow but the problem was that this solution is almost 5 years old. so I decided to spend few minutes writing a quick tutorial on how I achieved my goal and created my own custom validation rule influenced by the solution I found on StackOverFlow.

Let's get started 🚀

1- Input:

This is a simple HTML input tag that supports specific picture formats as well as multiple file uploads. The input is wrapped with a form that POST the files to the 'images/upload route.'

    <form action="images/upload" method="POST">
        <input
            type="file"
            id="thumbnail"
            name="thumbnail"
            class="h-full w-full z-50 opacity-0 cursor-pointer"
            accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
            multiple
            required
        />
    </form>
Enter fullscreen mode Exit fullscreen mode

2- Routes:

In routes/web.php file, we're using the ImageUploadController@store to handle post requests to this route.

Route::post('images/upload', [ImageUploadController::class, 'store'])->name('images.upload');
Enter fullscreen mode Exit fullscreen mode

3- Validation:

I usually don't ever do any validation within my controller, I follow the documentation to create a new request with php artisan make:request StoreImagesRequest and then Add my validation as showing below.

    public function rules()
    {
        return [

            'images' => ['bail', 'required'],
            'images.*' => ['bail', 'image', 'mimes:jpeg,jpg,png,gif,webp']
        ];
    }
Enter fullscreen mode Exit fullscreen mode

It really won't matter if you do your validation inside the controller store method, my solution will work either ways.

4- Custom validation:

Following the documentation, next this step should be running the following command php artisan make:rule Maxsize --invokable and head to app/Rules/Maxsize file.

A rule object contains a single method: __invoke. This method receives the attribute name, its value, and a callback that should be invoked on failure with the validation error message.

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\InvokableRule;

class Maxsize implements InvokableRule
{
    /**
     * Run the validation rule.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @param  \Closure  $fail
     * @return void
     */
    public function __invoke($attribute, $value, $fail)
    {
        // Logic goes here
    }
}
Enter fullscreen mode Exit fullscreen mode

The above code shows an empty rule that receives Attribute, Value, and Fail. All we need to do is code our validation logic in the invoked function and deliver a response if it fails. If you did not specify an attribute, like in our case, disregard the '$attribute' variable. Your input data will be stored in the '$value' variable, and we can define a response if our validation fails by assigning a string response to the '$fail' variable.

Now, let's get back to our goal. What we want to do here is loop over an array of files, compute the total file size of each file, and then send a failure response if the result is greater than 10.000 kilobytes.

For that, we're going to use array_reduce php function.

array_reduce — Iteratively reduce the array to a single value using a callback function

    public function __invoke($attribute, $value, $fail)
    {
        $total_size = array_reduce($value, function ($sum, $item) {
            // each item is UploadedFile Object
            $sum += filesize($item->path());
            return $sum;
        });

        if ($total_size > 10000 * 1000) {
            $fail('Total images size should be maximum 10 Megabytes');
        }
    }
Enter fullscreen mode Exit fullscreen mode

In this code, we're returning a single value out of the array by calculating the total file size. To calculate each file size we're using PHP's function filesize and passing to it the file path using Laravel's helper function path()

And then checking if the total size is over than 10mb then returning the $fail variable value.

Now we can use our Validation rule by importing it to whatever place we're using for validation, in my case StoreImagesRequest.php

<?php

namespace App\Http\Requests;

use App\Rules\Maxsize;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreImagesRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [

            'images' => ['bail', 'required', new Maxsize],
            'images.*' => ['bail', 'image', 'mimes:jpeg,jpg,png,gif,webp']
        ];
    }
}

Enter fullscreen mode Exit fullscreen mode

I hope this was a clear explanation and please let me know in the comments if you have any questions 👋

. . . . . . . . . . . . . . . . . . . . . . . . .