Create Custom Laravel Validation Rule for Total Uploaded Files Size 🚫✅
Tags: laravel, php, tutorial, help
Published: 2022-07-10 11:06:55

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](https://stackoverflow.com/questions/47054391/laravel-validate-array-of-files-total-allowable-upload-size/47056662#47056662) 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 🚀

| Table of Content              |
| ----------------------------- |
| [1- Input](#chapter-1)     |
| [2- Routes](#chapter-2)    |
| [3- Validation](#chapter-3)     |
| [4- Custom validation](#chapter-4) |


## 1- Input:<a name="chapter-1"></a>
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.'

```html
    <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>
```

## 2- Routes:<a name="chapter-2"></a>
In `routes/web.php` file, we're using the ImageUploadController@store to handle post requests to this route.

```php
Route::post('images/upload', [ImageUploadController::class, 'store'])->name('images.upload');
```

## 3- Validation:<a name="chapter-3"></a>
I usually don't ever do any validation within my controller, I follow the [documentation](https://laravel.com/docs/9.x/validation#creating-form-requests) to create a new request with `php artisan make:request StoreImagesRequest` and then Add my validation as showing below.

```php
    public function rules()
    {
        return [

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

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

## 4- Custom validation:<a name="chapter-4"></a>
Following the [documentation](https://laravel.com/docs/9.x/validation#custom-validation-rules), 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
<?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
    }
}
```

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](https://www.php.net/manual/en/function.array-reduce.php).

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

```php
    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');
        }
    }
```

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](https://www.php.net/manual/en/function.filesize.php) and passing to it the file path using Laravel's [helper function `path()`](https://laravel.com/docs/9.x/filesystem#file-paths)

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
<?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']
        ];
    }
}

```

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