
Article updated on 20 February 2022 for the new Laravel 9 release
Laravel Eloquent is one of the flagship features of the Laravel framework, largely thanks to how well it handles defining, creating and managing relationships between database tables. In this tutorial I will show you how to create and use Eloquent relationships, so that you can be productive with no prior knowledge of them. I also suggest reading the Laravel 9 tutorial for beginners, which offers another way to learn Laravel with Eloquent.
What is a database relationship?
Let us start with the absolute basics. What exactly is a relationship? A relationship means you have two or more tables whose records are linked to each other. Say you have a users table and each user can have several posts. How do you connect the two? Usually by adding a user_id column to the posts table, so you can easily tell which user each post belongs to. That “connection”, which lets you work out which records belong together, is called a relationship.
What types of Eloquent relationship exist?
There are several types of Eloquent relationship. The one you need usually depends on whether you have one or (potentially) several items on each side, in the first table and in the second.
In the example above we have a users table and a posts table. Say each post can only belong to one user. To implement that, we add a user_id column to the posts table, so that every row of posts points at its author. In the opposite, far less likely scenario, if each post could belong to several authors (users) and each author could only contribute to a single post, we would add a post_id column to the users table. This is a fairly simple case, a One-To-Many relationship.
But what if each user can have several posts, and each post can have several authors? How do we solve that? This is what we call a Many-To-Many relationship. For this more complex type we cannot simply add a column to a table and call it a day: we need a pivot table. Do not worry if that sounds complicated, we will come back to it shortly.
Broadly speaking, these are the types of relationship you will find between data:
- The “has one” or one-to-one relationship
- The one-to-many relationship (One-To-Many)
- Has-One-Of-Many relationships (for example the latest of many)
- HasOneThrough and HasManyThrough relationships
- The Many-To-Many relationship
One-to-one relationships with Eloquent
Let us start with the simplest relationship. A “Has One” or “One-To-One” relationship. It means that a given record is linked to one other record, and not to several.
Carrying on with the blog example, with users and posts, say each user can have one profile. In some cases you might store all the profile information on the User model, but that would not be ideal. Here I want it in a separate table. If we later want to hand a profile over to a different user, for instance, that will come in handy.
First, create a Profile model, the User one is generated by default. The exact columns of the Profile model do not matter much, but you will want to create the model and its migration together:
php artisan make:model Profile -mBecause this is a one-to-one relationship, we get the rare luxury of deciding whether:
- we put a
user_idcolumn on theProfilemodel, or - we add a
profile_idcolumn on theUsermodel.
Either works, but bear in mind that the relationship describes a User that has a Profile. And when you say that something has something else, you usually add the column to the second model. You could say the first model “owns” the second one.
So here is what I am going to do:
/** Add this to your profile table migration, or create a new migration */
$table->foreignId('user_id');Run the migrations and we are ready to implement this.
Open your User model and add the following public function. You are free to name the function whatever you like, but the convention is snake_case:
public function profile()
{
return $this->hasOne(Profile::class);
}The hasOne call now expects the profile() function to use the user_id column of the Profile model. If your column is named differently, pass a second argument to hasOne with the other column name:
return $this->hasOne(Profile::class, 'author_id');That said, straying from the convention is rarely a good idea.
You have now added your first Eloquent relationship! Scroll down to learn how to use it.
Adding the inverse of this relationship
In most cases we can also define the inverse of a relationship. That sounds complicated, but it really is not. It simply means adding a function to the Profile model that points back to the model it belongs to (User here). In your Profile model:
public function user()
{
return $this->belongsTo(User::class, 'author_id');
}Using an Eloquent relationship
Using this One-To-One Eloquent relationship is very simple. Whenever you have an instance of the User object you can call $user->profile or $user->profile()->enchainerDautreMethodeIci().
That works because $user is an instance of the User class, and we added the profile() relationship method to that model.
Note, and this matters, that with this method you can reach both ->profile as if it were a property and ->profile() as if it were a function. The difference is that ->profile returns an Eloquent instance, whereas ->profile() lets you chain more methods onto it. For example ->profile()->orderBy('xxx', 'ASC')->get().
In short, ->profile is a shortcut for ->profile()->get().
$user = auth()->user();
$profile = $user->profile;
$name = $user->profile->display_name;
// Create a profile
$profile = $user->profile()->create([
//
]);
$user = Profile::find(1)->user;The One-To-Many Eloquent relationship
Another very important relationship, perhaps the most important of all, is the one-to-many relationship. Also known as the hasMany relationship, it describes one item that has many other items. It is very close to the previous one.
To carry on with the blog example, say a profile has many posts. Open your Profile model and add the following method:
public function posts()
{
return $this->hasMany(Post::class);
//Or return $this->hasMany(Post::class, 'foreign_key');
}This means each profile has many posts. The inverse exists too:
public function profile()
{
return $this->belongsTo(Profile::class);
//Or return $this->belongsTo(Profile::class, 'foreign_key');
}You use it much like the example above, except that this relationship returns several items, as an Eloquent collection.
Now that the relationship is defined as a function on the model, we can again use it as a property (->posts), which returns an Eloquent collection here instead of a single model. And we can also use it as a function (->posts()->where('created_at', '>', now()->subDays(14))->get() ).
$posts = Profile::find(1)->posts;
//Or $posts = Profile::find(1)->posts()->get();
foreach ($posts as post) {
// TODO
}
$lastPost = Profile::find(1)->posts()->latest()->first();Getting the newest or the oldest model through a relationship
Sometimes, when you define a hasMany relationship, you only want to fetch the newest or the oldest model. Laravel gives you two handy methods for that, ->latestOfMany() and ->oldestOfMany():
public function latestPost()
{
return $this->hasMany(Post::class)->latestOfMany();
}public function oldestPost()
{
return $this->hasMany(Post::class)->oldestOfMany();
}Both of these return a single model instead of a collection.
$latestPost = Profile::find(1)->latestPost;If that suits you but you need custom filters or more advanced where() clauses, take a look at this example from the Laravel documentation:
/**
* Get the current price of the product
*/
public function currentPricing()
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}HasOneThrough and HasManyThrough
Now that we have seen those examples, let us go a step further. This time we want to define a relationship through another model.
Take our example above, where each User has a Profile and each Profile has many Posts. Fetching every post of a user is a perfect example of a relationship through another model. We cannot add a hasMany(Post::class) to our User model directly, because the Post model does not hold a user_id. It only has a profile_id. This is a textbook has many through relationship.
Defining such a relationship works much like what we have seen above. In our example, add the following to the User model:
public function posts()
{
return $this->hasManyThrough(Post::class, Profile::class);
}The first argument of this function is the model we want to reach and the second one is the intermediate model.
Defining a hasOneThrough relationship is almost identical to a hasMany(), except that here you have to make sure there is only ever one item at the end:
public function first_login()
{
return $this->hasOneThrough(FirstLogin::class, Profile::class);
}Many-to-many relationships
Now that we have covered the relationships above, let us move on to something more advanced. A many-to-many relationship is one where each User can have several Profiles, for instance when working alongside other people, and each Profile can have several Users.
How do we solve that? We cannot simply add a profile_id to the users table, because there are potentially several profiles. And we cannot add a user_id to the Profile model either, because there are potentially several users as well.
To solve it we need some kind of intermediate table. That intermediate table is called a pivot table, and in most cases its only job is to store two id values in the same row.
Consider the following: if our pivot table has a user_id and a profile_id, we can now link a given User to a given Profile. And because the same user_id can appear as often as we like, we can connect each user to as many profiles as we like. The same goes for profiles, of course: we can repeat the same profile_id as often as we want, next to as many different user_id values as we want, so a given Profile can belong to as many users as we want.
How do you create a migration for a Laravel pivot table?
First, we create the database migration we need. Run the following command. Notice how the table name is built by joining the two table names (both singular) in alphabetical order. You can override it, but there is usually no point.
php artisan make:migration create_profile_user_table --create=profile_userNow open the migration file and add two more lines to the up() method.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProfileUserTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('profile_user', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('profile_id');
$table->foreignId('user_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('profile_user');
}
}
As you can see, the structure of a pivot table is very simple. Now run the migrations.
Next, open both models and add the two relationship functions. On each model the method is a belongsToMany().
Add this to your User model:
/**
* Public function to get every profile this user can publish under
*/
public function profiles()
{
return $this->belongsToMany(Profile::class);
}And this to your Profile model:
/**
* Public function to get every user who can publish under this profile
*/
public function users()
{
return $this->belongsToMany(User::class);
}If you pick another table name than the default one, pass your own table name as a parameter to the belongsToMany functions:
return $this->belongsToMany(Profile::class, 'user_role'); // The table here is in reverse alphabetical orderBy default, Eloquent expects the user_id and profile_id columns on the pivot table. If you name them differently, pass them as the third and fourth parameters:
return $this->belongsToMany(Profile::class, 'user_role', 'nom_de_la_colonne_qui_represente_l_identifiant_de_ce_modele', 'nom_de_la_colonne_qui_represente_l_identifiant_de_la_cle_etrangere');
// This looks needlessly complex, so here is an example.
// Say we add this to the User model; by default it would look like this:
return $this->belongsToMany(Profile::class, 'user_role', 'user_id', 'profile_id');Using an Eloquent many-to-many relationship
Using this relationship is very easy, and it will look familiar after what we have seen above. Have a look at these short examples:
$user = User::find(1);
$user->profiles->each(function ($item) use ($user) {
$profile->name = $user->firstname . ' ' . $user->lastname;
$profile->save();
});$profile = Profile::find(1);
$profile->users()->orderBy('created_at', 'desc');Storing data in the pivot table
The last part of this article is about storing data in the pivot table. Yes, that works too. It is not needed in every project, but there are certainly cases where it pays off.
To recap: what is a row in a pivot table? What does it stand for? A row in a pivot table stands for the relationship between two records. If we deleted a given row, would the relationship still exist? No.
That is why this matters. If each row of a pivot table stands for a relationship, we can also store information about that relationship in the pivot table. It can be something as trivial as when the relationship was created or last updated (with the created_at and updated_at columns).
Using a pivot table to retrieve relationship data
In the example above we wrote a migration for the pivot table. That migration already created our created_at and updated_at columns (with $table->timestamps();). So how do we reach them?
First we have to tell Eloquent: our pivot model has a few more attributes I want to access, here they are.
You can do it like this:
/**
* Public function to get every profile this user can publish under
*/
public function profiles()
{
return $this->belongsToMany(Profile::class)->withPivot('created_at', 'updated_at', 'active');
}Or use the rather nice helper ->withTimestamps() to declare the created_at and updated_at columns in one go:
/**
* Public function to get every profile this user can publish under
*/
public function profiles()
{
return $this->belongsToMany(Profile::class)->withTimestamps()->withPivot('active');
}You can now read the pivot table like this:
$user = User::find(1);
foreach ($user->profiles as $profile) {
echo $profile->pivot->created_at;
}Handy, is it not?
Renaming your pivot attribute
There is one more neat trick you can use, and that is renaming the pivot attribute. Eloquent uses descriptive, expressive language. The documentation gives the example of a relationship between a podcast and a user. That relationship is a subscription, so calling it “pivot” would read a little oddly.
// Bad:
$user->podcasts()->first()->pivot->price;
// Good:
$user->podcasts()->first()->subscription->price;To rename the pivot attribute, call ->as($name) on the return $this->belongsToMany() chain:
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();Conclusion
That was a long article. If you are still reading, thank you for sticking with me. In this article I went through all the different types of relationship between Eloquent models. I hope it has helped you on your way to understanding Eloquent relationships.
The relationships shown here are the ones you will use most. In my view they are more than enough for now.
You may still run into situations where these relationships are not enough. So far we have only talked about the relationship between one model and another model. But consider the fairly common case where you can tag both a Post and a Page. The relationships above will not cover that: we have not looked at polymorphic relationships. I have already written an introduction to Eloquent polymorphic relationships, so do read it if you want to dig deeper


