Laravel file upload tutorial – Handling File Uploads in Laravel
In this Laravel file upload tutorial, you will learn how to handle file uploads in Laravel. Almost all applications have to do with file upload of some sort: a profile picture, a document for KYC verification, a document containing CV, etc.
Hence, it is important to understand how to handle file uploads in Laravel.
In this tutorial, we are going to build a simple user profile form, where the user will need to fill his details and these details will include submitting a profile picture.
At the end of this tutorial, you should be able to handle file uploads, handle file upload validation, and know what to do when the file doesn’t get uploaded successfully.
Setting Up
To get started, Laravel needs to be installed on your system. If you have Laravel installed already, you can skip this step.
I would advice that you install Laravel globally. Especially if you plan on using it often. You can do so by running the following command on a terminal.
composer global require laravel/installer
Once that is complete, we can go ahead to create a new Laravel project for this tutorial.
laravel new profile-form
Running the above command will create a new Laravel project in the current directory called profile-form.
Serve the app by running the following:
cd profile-form
php artisan serve
Navigate to http://localhost:8000/
where you’ll find your app showing the default Laravel landing page.
Writing the Database Migration
We will need to create a users table which will store user details.Laravel ships with a migration for the users table which can be found at database\migrations\2014_10_12_000000_create_users_table.php
. Modify that file to look like this:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('photo');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
Here, we are have modified the default laravel migration for the users table to include a field photo which will store the URL to the users photograph.
Modify the .env file to include the correct database details. Then run the migrations by running the following command:
php artisan migrate
Update the User model to reflect the addition of the photo field. Add photo to the $fillable
so it can be autofilled by laravel when creating and updating a user.
// App\User.php
protected $fillable = [
'name', 'email', 'password', 'photo'
];
Once that is done, we can proceed to build the frontend of the profile form.
Building the Frontend
We will need to create an authentication system which will allow the user to register and login into the application.
Create a new file in resources/views called register.blade.php
. Add the following to the newly created file.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Register | Profile Form</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<!-- Styles -->
<style>
.form-box{
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
margin-bottom: 10px
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-sm navbar-light bg-light">
<a class="navbar-brand" href="#">Profile Form</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Register <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Login</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
<div class="container">
<h1>Register</h1>
<section class="form-box">
<form action="#" class="col-md-5">
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" id="name" name="name" class="form-control" required>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" name="email" id="email" class="form-control" required>
</div>
<div class="form-group">
<label for="password">
Password
</label>
<input type="password" name="password" id="password" class="form-control" required>
</div>
<div class="form-group">
<label for="password_confirmation">Confirm Password</label>
<input type="password" name="password_confirmation" id="password_confirmation" class="form-control" required>
</div>
<div class="form-group">
<label for="photo">Attach a photograph</label>
<input type="file" name="photo" id="photo" class="form-control-file" required>
</div>
<div class="form-group">
<button type="submit" class="btn btn-outline-primary">Submit</button>
</div>
</form>
</section>
</div>
</body>
</html>
We are using bootstrap for styling, hence the need to add it to the top section of the page. We’ve made all the fields required here so the user won’t be able to submit the form without filling all the fields.
The field that is particularly important to us here is the file input field <input type="file"
Update web.php to include the route to the register page
// web.php
...
Route::get('/register', function () {
return view('register');
});
Going to http://localhost:8000/register
will show the register page containing the fields that are typical of a registration form.
We need to specify the type of files that are supported. Since we want only image files, we need to specify in the input element that we want just image files.
Modfiy the input:type="file"
to accept only image files by adding the accept attribute with a value of image/*
to the input tag.
The photograph section should now like:
<div class="form-group">
<label for="photo">Attach a photograph</label>
<input type="file" name="photo" id="photo" accept="image/*" class="form-control-file">
</div>
While the form looks set to send images to the backend, it can not do that yet. We need to tell the form that it will need to send another type of form data to the backend, in this case binary data.
We do this by adding the enctype
attribute to the form element, and setting it’s value to multipart/form-data
.
The opening form tag should look like:
<form action="#" class="col-md-5" enctype="multipart/form-data">
Next, we need to write the controller to handle the upload.
Writing the Controller
Our backend should be able to handle the validation, storing and redirecting/authentication the user.
Laravel ships with a RegisterController which can be found at app\Http\Controllers\Auth\RegisterController.php
.
Validation
The validator
method in the Register Controller is tasked with the responsibility of validating the fields. So let’s update it to reflect what we actually want.
Modify the validator method in RegisterController.php to validate all our fields.
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'photo' => ['required', 'image']
]);
}
The name field should be a required field, and a string, hence the validation rules. Similar goes for email and password.
The password field needs to be confirmed. In the frontend, we added a field for password_confirmation. This is what the password field is compared with. Finally the photo field.
This field is required when registration. It is required because in our database migration, we made it so. If we had set it to be nullable, then we wouldn’t need to set the validation to required.
Laravel also has a handy validation rule, image, which is used to validate image files (files with either of the following extensions: jpeg, png, bmp, gif, or svg).
Some other validation which you might want to be carried out on files are
- Ensuring the file size is not greater or smaller than a specific amount. This rule can be added as
max:500
ormin:500
as required. It should be noted that the file size here is in kilobytes - Checking the dimensions of the image. This can be done by adding the following to the array of rules
'dimensions:min_width=100,min_height=200'
.
Storing
The next step is storing the image. We will create a new function to handle this functionality.
There are several places where files can be stored in an application: they could be stored in an s3 bucket, google drive, or even in your application server.
Laravel comes with support for storing files on these locations. For simplicity, we will be storing the image file in the application.
Laravel Request class, Illuminate\Http\Request
provides support for file handling out of the box. The uploaded file can be stored via $request→file(<inputName>)→store(<folderToBeStored>)
. For our case, we could use:
$request->file('photo')->store('profile')
This stores the file in the storage \app\profile
folder. The file is saved with a random name, and the url to the file is returned.
If we want to assign a custom name to the image as it is being stored, we need to use the storeAs
method.
Using the storeAs
method will look like
$request→file(<inputName>)→storeAs(<folderToBeStored>, <customName.fileExtension>)
The file extension is important, and laravel provides simple ways to get the file extension of the file that was uploaded, via the clientExtension
method.
It can be gotten as shown below
$request->file('photo')->extension()
Using this method, we can save a user’s image based on the user’s name.
$fileName = $request->get('name') . '.' . $request->file('photo')->extension();
$request->file('photo')->storeAs('profile', $fileName);
In our case, we can make do with the random names which laravel saves our file.
- For your file to be accessed publicly, it needs to be stored in the
storage/app/public
folder.
- Files in other folders will not be publicly available
Let’s create a function that handles the task of saving images to the storage.
Underneath the validator function, add the following
protected function storeImage(Request $request) {
$path = $request->file('photo')->store('public/profile');
return substr($path, strlen('public/'));
}
The storeImage
function stores the image in the storage/app/public/profile
folder and returns a URL to the storage location of the file.
Putting it all together
Since we are adding more things to be done, we need to override the default register method provided for us by laravel.
Laravel provides a trait for registering users and can be found at
Illuminate\Foundation\Auth\RegistersUsers.php
.
Update the Register Controller to include the following:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'photo' => $data['photo']
]);
}
public function register(Request $request)
{
$this->validator($request->all())->validate();
$imageUrl = $this->storeImage($request);
$data = $request->all();
$data['photo'] = $imageUrl;
$user = $this->create($data);
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
As seen above, the create function does the work of creating a user based on the data that is sent to it.
The register function is where everything happens. The validation is first carried out before any other thing.
If the validation is passed, we store the image. After that is done, we create the user by passing all the data that’s needed in an array, $data
.
If everything happens successfully, we authenticate the user and redirect to the home page.
Showing the Uploaded Image
The home page is where we will display all the user details. First, let’s create a route and controller for it.
On the terminal, run the code to generate the HomeController.
php artisan make:controller HomeController
Go to the newly created HomeController
, and add the following methods to the class.
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
class HomeController extends Controller
{
function __construct()
{
$this->middleware('auth');
}
public function show()
{
return view('home')->with('user', Auth::user());
}
}
In the constructor, we define that we are using the auth middleware. What this middleware does is that it allows only authorized users to access functions defined in the class.
Hence, the user can only come here if he is authenticated. If the user is unauthenticated, he is redirected to the login page.
The show function handles the displaying of the home page. We are passing the authenticated user to the view as well. To learn more about this, check the laravel documentation.
Update the routes file to contain the home route.
// web.php
...
Route::get('/home', 'HomeController@show')->name('home');
Finally create a home.blade.php file in the resources/views
directory.
Add the following to home.blade.php:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home | Profile Form</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 500px;
}
.card {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 300px;
padding: 50px 0;
}
.card-img-top {
width: 200px;
height: 200px;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="card">
<img src="https://via.placeholder.com/150" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">{{$user->name}}</h5>
<p class="card-text">{{$user->email}}</p>
<a class="btn btn-warning">Logout</a>
</div>
</div>
</body>
</html>
In the view file, we have access to the user
data that was passed from the controller.
We are currently using a placeholder image to show the user’s photo because the photo is not currently available to us.
We need to create a symbolic link from public/storage
to storage/app/public
folder to Laravel makes this simple by providing the artisan command:
php artisan storage:link
After doing that, update the image tag to show the profile image.
<img src="{{asset('storage/'.$user->photo)}}" class="card-img-top" alt="...">
Congrats!!! You have successfully learnt how to store images in laravel.
Conclusion
Almost all applications require storing files in one way or another. Following the above guide, you should be able to store images and any other file to the local storage.All the code used here can be found here on Github.