Laravel 5.8 From Scratch: Eloquent Relationships and Image Upload

Sagar Maheshwary
7 min readApr 10, 2019

--

In the previous tutorial, we had implemented authentication, email verification and password reset. We also learned about implementing manual authentication, middleware, and used mailtrap for sending reset and verification emails. Now what we want to do is create a new column “user_id” and relate it to users table with a foreign key so a user can only access his/her todos and not other todos. We will implement one to many and one to many (inverse) relationship. Lastly, I want you guys to learn image upload so we will create a new column “image” in users table and upload image to the laravel public disk.

Eloquent Relationships

Most of the times, a table relates to another table. Eloquent provides several different types of relationships for managing those table relationships. They are many relationships but we will only talk about the easy ones (I don’t want to overwhelm beginners):

  • One to Many
  • One to Many (inverse)

One to Many

One to many relationship is used when there’s a model that owns any amount of other model. In our application, there’s our User model that can own any amount of Todo models. Another example Post model that can own any amount of Comment models. Eloquent relationships are defined inside the models. Before defining the relationship, we need to define the foreign key column in our todos table so let’s create a new migration:

php artisan make:migration add_user_id_column_and_foreign_constraint_to_todos_table --table=todos

When creating migrations, it’s name should define what it’s doing (it doesn’t have to be perfect). When you are trying to update an existing table you need to use “— table” instead of “ — create” which will assume that we are updating an existing table. Open the newly created migration file and inside up() method add the following:

You can not create a foreign key inside a table with rows so we are first truncating the table and then creating a new column. We also have Schema::table() instead of Schema::create() and that’s because Schema::create() is used for a new table and Schema::table() is used for updating an existing table. Inside Schema::table() we are creating an unsigned big integer column named “user_id”. After that, we are creating a foreign key constraint on “user_id”. For our down() method, we will first drop the foreign key and then “user_id” column:

To drop foreign keys, you can pass the column names as an array to the $table->dropForeign() method. Let’s run the migrations:

php artisan migrate

Now we can define the relationship inside the Todo model:

hasMany() is the one to many relationship and we are using $this because laravel models are extending (inheriting) eloquent models. First parameter of a relationship is the model and second parameter is the foreign key which is optional and by default laravel will try to determine the foreign key by the model name e.g user_id in Todo model if it relates to User model or post_id in Comment model if it relates to Post model. You can retrieve all the todos related to a certain user by calling the relationship as a property:

Above we are first retrieving the user with an “id” of “1” and then we are retrieving all the todos that belongs to that specified user. All the eloquent relationships are QueryBuilders so we can chain them with constraints. For example, we want to retrieve only completed todos so we can chain it with where() method (When adding constraints then use them as a method not property, like below example):

Now we want to show only user related todos so open the TodosController and modify the Index() method:

Instead of retrieving all the todos, we are only retrieving the todos that are related to the authenticated user with todos() relationship. Last thing we want to do is specify the “user_id” when we create a todo so when creating a Todo. Modify the Store() method with:

One to Many (Inverse)

This relationship is the inverse of one to many relationship. For example many Todo models can have one User model or many Comment models can have a Single Post model. Open the Todo model and add the following method:

Now we will show the name of the user when displaying all the todos. Although it does seem stupid to show the name of the user when we already know that todo was created by the authenticated user but it’s just for example. Open the index.blade.php from the todos folder and update the markup inside the @forelse() loop:

Just like one to many relationship, you can access the user that owns the todos with $todo->user property.

Image Upload

Image upload is the last section of our series. Just for demonstration purposes we will create a profile view, where we can update the profile of a user as well as upload a profile image. Laravel supports local storage as well as cloud storage like s3, dropbox, and many more. Local storage also known as the “public disk” which simply means that all the uploaded files will be stored in “storage” directory of our laravel application. Laravel provides a way to access files from the “storage/public” by creating a symbolic link with artisan command so we can retrieve these files from the public directory:

php artisan storage:link

Now that our storage is linked, we will create a migration for “image” column where we will store the image name:

php artisan make:migration add_image_column_to_users_table --table=users

Open the migration and add a string column with a default value of “image.png” (because we don’t upload during the user creation). you can also use “null” instead of a default value:

Inside the down() method, we will drop the column as specified in above migration. Run the migrations with “migrate” command and open the web.php routes file so we can add new routes:

Open the navbar.blade.php and add the profile link inside the dropdown menu:

Let’s create that ProfileController where we will update the profile:

php artisan make:controller ProfileController

Let’s add “auth” middleware to our ProfileController so only authenticated users can access the methods:

In this controller, we will have two methods Index() and Update(). Index() will return a view where we can update the user profile and Update() will have the actual logic for updating the user profile. Let’s start with Index():

The Update() method will have quite a bit of code so I will be breaking it into a few steps (Don’t worry it’s not complicated):

  • Validate the form.
  • Check if the image is uploaded or not. If it is, then upload the new image and delete the previous image if exists. Lastly, assign it to user image property.
  • Check if the password field is empty, if it is then keep the old password.
  • Call the save() method and redirect to the specified route.

First import all classes in ProfileController:

Let’s define the Update() method:

Before doing anything in Update() method, let’s look at some request methods for uploaded files:

Now everything we will do needs to be put inside the update() method. Let’s start with validation:

“nullable” is used for optional fields so if we leave the “password” and “image” field empty, it will still pass the validation. “image” rule will check if the uploaded file is an image and it allows few formats as define above. This time “max” will check the file size and the size is specified in kilobytes so we are only allowing images that are “1999kb” in size. Now we will retrieve the authenticated user, upload the image and save the user to the database:

We have assigned the “name” and “email” fields. Now we will upload the image:

With laravel, you can retrieve the full name of the file with getClientOriginalName() and for extension you can use getClientOriginalExtension(). we are concatenating the extension with uniqid() function which will generate a unique string. We are also deleting the previous image with Storage::delete(‘path/to/filename’) and don’t worry Storage::delete() doesn’t generate any error if the file is not found.

Don’t use uniqid() for security purposes. use random_bytes() instead.

Now we will check if the password is not empty and then we will first hashing it and assign it to the password field. If it is empty then we will keep the old password:

Finally we can call the save() method and redirect back:

The complete method Looks like this:

Now that our controller is done, we can create the view so go to the views directory and create a folder named “user” and inside that we will have a view named profile.blade.php. Open that view and add the markup:

This view is same as the other views but we are uploading a file with this form so we need to specify the “enctype” attribute of our form and it’s value will be “multipart/form-data”. Last thing we need to do is display that uploaded image and we will display it on the user dropdown menu so open the navbar.blade.php again and inside the dropdown menu, update:

With:

Since we have created a symbolic link to “storage/public” directory so now we can use asset() helper to generate the url. Our images are stored inside the pics folder so we are concatenating the “storage/pics/” with image name from the authenticated user.

There is an important helper which we did not talk about in this series and that helper is dd() helper which will kill the page and dump the data passed to it. You can pass a single value as well as an array to dd() helper. This helper is very useful in development.

That concludes our series “Laravel 5.8 From Scratch”. Feel free to comment below or create an issue in the github repo.

Source code for this tutorial series:

--

--