
In this tutorial, you will upgrade the upload form to upload multiple photos.
Setup
Follow the instructions at README.
Multiple uploads
The challenge increases when the thing goes plural. Validating a single uploaded files is simple. Validating multiple uploaded file with the same rule leads to redundant code.
Today, you going to upload an array of photo and validate them.
Craft user interface
You do a little bit upgrade on the form. You add a file input which sits side by side with the previous one.
// resources/views/index.blade.php
<form method="POST" action="/" enctype="multipart/form-data">
{{ csrf_field() }}
@if ($errors->has('photo'))
<div class="alert alert-danger" role="alert">
{{ $errors->first('photo') }}
</div>
@endif
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h4 class="card-title">Photo 1</h4>
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo" class="custom-file-input">
<span class="custom-file-control"></span>
</label>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h4 class="card-title">Photo 2</h4>
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo" class="custom-file-input">
<span class="custom-file-control"></span>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="row pt-2">
<div class="col">
<button type="submit" class="btn btn-primary btn-lg btn-block">Submit</button>
</div>
</div>
</form>

Validate the array
In Laravel, you can validate an array with “dot notation”.
You set the photo to be an array.
// resources/views/index.blade.php
...
<div class="card-body">
<h4 class="card-title">Photo 1</h4>
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo[]" class="custom-file-input">
<span class="custom-file-control"></span>
</label>
</div>
</div>
...
<div class="card-body">
<h4 class="card-title">Photo 2</h4>
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo[]" class="custom-file-input">
<span class="custom-file-control"></span>
</label>
</div>
</div>
...
You set the dot notation in the previous rule.
// app/Http/Requests/UploadPhotoRequest.php
public function rules()
{
return [
'photo.*' => 'required|max:1024|mimes:jpeg,png'
];
}
Try to select a nonimage for Photo 1 and submit.

You’ll see the error message shows that the photo.0 is not an image.
Dot notation and required rule
The * character within dot notation represents dynamic numbers, which means the number doesn’t exist until you submit it.
You can submit photo.0, photo.0 and photo.1, or none. Laravel have no idea what is the length of the array, so it cannot applies the required rule.
Therefore, you can use the required in HTML5 to ensure user to select a file.
// resources/views/index.blade.php
...
<div class="card-body">
<h4 class="card-title">Photo 1</h4>
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo[]" class="custom-file-input” required>
<span class="custom-file-control"></span>
</label>
</div>
</div>
...
<div class="card-body">
<h4 class="card-title">Photo 2</h4>
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo[]" class="custom-file-input” required>
<span class="custom-file-control"></span>
</label>
</div>
</div>
...
Custom error message
You can always override the default Laravel’s error message to deliver a meaningful and relevant message.
// app/Http/Requests/UploadPhotoRequest.php
public function messages()
{
return [
'photo.*.required' => 'You haven\'t choose a photo.',
'photo.*.max' => 'Your photo is too large, must be less than :max kb.',
'photo.*.mimes' => 'We only accept :values.',
];
}
Display the error message on the right input.
// resources/views/index.blade.php
<form method="POST" action="/" enctype="multipart/form-data">
...
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h4 class="card-title">Photo 1</h4>
@if ($errors->has('photo.0'))
<div class="alert alert-danger" role="alert">
{{ $errors->first('photo.0') }}
</div>
@endif
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo[]" class="custom-file-input" required>
<span class="custom-file-control"></span>
</label>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h4 class="card-title">Photo 2</h4>
@if ($errors->has('photo.1'))
<div class="alert alert-danger" role="alert">
{{ $errors->first('photo.1') }}
</div>
@endif
<div class="form-group">
<label class="custom-file">
<input type="file" name="photo[]" class="custom-file-input" required>
<span class="custom-file-control"></span>
</label>
</div>
</div>
</div>
</div>
</div>
...
</form>
Now, submit a photo for Photo 1 and a nonimage for Photo 2. You’ll see the error message displays on the Photo 2 only.

Store file
While uploading multiple files, storing the files with the same name will override former file. So, you append an index number after the file name.
// app/Http/Controllers/PhotoController.php
public function store(UploadPhotoRequest $request)
{
foreach ($request->file('photo.*') as $key => $file) {
$extension = $file->extension();
$path = $file->storeAs('images', "my_photo.$key.$extension");
}
return view('success');
}

What’s next
Next tutorial will be talking about saving into a database.
If you haven’t subscribed to my email list on the homepage, do it now. I’ll notify you when the new tutorial is published.
Did I tell you that you’ll receive a series of FREE tutorials when you subscribe? Go check it out.
Stay tuned.
Originally published at I Teach You How To Code.