Laravel Eloquent Relations: An Advanced Guide - (r)

Jun 6, 2023
Two people hugging and learning about Laravel relationships

Share on

Often, there comes a moment that every developer encounters where you have to interact with databases. This is the point at which Eloquent is the Laravel object-relational modeler (ORM) allows the interaction process with the tables of your database easy and easy.

As an expert, you recognize and understand the 6 key types of relationships which we'll explore and then review.

What Relationships Do Eloquentians Have in Their Language?

When working with tables in the relational database, you identify relationships as the connections between tables. This allows you to manage and structure your data quickly which allows for better readability as well as the handling of the data. There are three kinds of database relationships in practice:

  • one-to-one - One record in the table can be linked to one, and only one table. This could be, for example, a name and an social security number.
  • one-to-many - One record is associated with multiple records in another table. Like a writer and blogs.
  • multiple-to-many records - Multiple entries in tables are linked with several records from another table. For example, students and the courses they are enrolled in.

Laravel provides a seamless way to connect and manage relationships with databases by using an object-oriented syntax within Eloquent.

Along with these definitions, Laravel adds additional relationships that include:

  • There are many ways to go through
  • Polymorphic Relations
  • Many-to-many Polymorphic

Take, for example an online store that contains a variety of articles, each within its own class. So, dividing the database into different tables makes sense from a practical point of view. It has its own challenges in its own right, since you do not want to search each table.

You can create a simple one-to-many relation using Laravel for us to assist in situations like when you need to inquire about product, and we are able to do this by using the Product model.

Database schema with three tables and a joint table representing a polymorphic relationship
A database schema that includes three tables as well as a joint table that represents the polymorphic relation

One-To-One Relationship

As the primary relation Laravel provides, they connect two tables in a way such that one row from the table in question is linked with only one row from the other table.

For a clearer picture of this it is necessary to build two models each with its individual migrations:

php artisan make:model Tenant 
 Php artisan make:model Rent

We currently are dealing with two models. One which is the Tenant, as well as the second being their Rent.

hasOne(Rent::class);
 
 

The reason is that eloquent decides on the foreign key relationship in relation to the parent model namesake (Tenant in this instance) and the Rent model presumes that there exists a tenant_id foreign key.

We are able to easily modify the method in this way by adding an argument to use the isOne method:

return $this- >hasOne(Rent::class, "custom_key");

Eloquent assumes there is a match between the specified foreign key with the main key of its parent (Tenant model). By default, it will seek to find the tenant_id with the id key in the Tenant record. It is possible to overwrite this by using another argument within the isOne method. In this case, it matches another key

return $this->hasOne(Rent::class, "custom_key", "other_key"); 

Once we've defined the one-to-one relationship between the models, we can use it easily, like this:

$rent = Tenant::find(10)->rent;

In this particular line it will calculate the tenant's rent using number 10 if the ID is there.

One-To-Many Relationship

Like the previous relationship, this will define connections between a single parent model and models with multiple children. The odds are that our Tenant will have only one Rent bill because it's a regular payment, therefore, it will likely have several payments.

Our prior relationship was not perfect, which we are able to fix:

hasMany(Rent::class);
 
 

Before we can call the procedure for calculating rents a good thing to know is that relationships are query builders. So you can add further limitations (like rent in between dates, min payments or min payment.) and then chain them together to achieve the desired outcome:

$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();

As with the previous one it is possible to overwrite both the local and foreign keys with further arguments:

return $this->hasMany(Rent::class, "foreign_key");
return $this->hasMany(Rent::class, "foreign_key", "local_key");

Now we have all the information about the rent of tenants however, what can we do when we know the amount of rent but want find out who the rent belongs? It is possible to use belongsToproperty: isToproperty:

belongsTo(Tenant::class);
 
 

And now we can rent the property easily:

$tenant = Rent::find(1)->tenant;

In using the isTo approach, we may additionally overwrite both keys for local and foreign just like we did previously.

Is One of Many Relationships

As our Tenant model is able to be in conjunction with a number of Rent models, it is important to quickly access the most recent or oldest related model of the relationships.

An efficient method of accomplishing this is by combining the hasOne ofMany and ofMany techniques:

public function latestRent() 
 return $this->hasOne(Rent::class)->latestOfMany();
 
 
 public function oldestRent() 
 return $this->hasOne(Rent::class)->oldestOfMany();
 

In default, we're being provided with data that is based upon the primary key. This is sorted. However, it is possible to create your own filters using the ofMany method.

return $this->hasOne(Rent::class)->ofMany('price', 'min');

HasOneThrough and HasManyThrough Relations

The throughmethods suggest that the models we use will need to be able to traverse through an different model in order to establish an association with the desired model. As an example, for instance, we could associate the Rent with the Landlord. However, the rent must pass through the Tenant to reach the Landlord.

The tables' keys required for this appear like:

rent
 id - integer
 name - string
 value - double
 
 tenants
 id - integer
 name - string
 rent_id - integer
 
 landlord
 id - integer
 name - string
 tenant_id - integer

When we've visualized how our tables look, we can make the models:

hasOneThrough(Landlord::class, Tenant::class);
 
 

The first argument of using the hasOneThrough method is the model that you wish to gain access to, while the second argument is the model you will go through.

Just like in the past it is possible to overwrite both the foreign and local keys. Now that we have two models, we have two copies of each in this order:

public function rentLandlord() 
 
 return $this->hasOneThrough(
 Landlord::class,
 Tenant::class,
 "rent_id", // Foreign key on the tenant table
 "tenant_id", // Foreign key on the landlord table
 "id", // Local key on the tenant class
 "id" // Local key on the tenant table
 );
 

Similar to similarly, the "Has Many Through" relation in Laravel Eloquent can be useful if you're looking to gain access to data in a distant table through the intermediate table. Consider an example using three tables:

  • the country
  • users
  • games

Each country has many players Each User is a participant in numerous Games. We want to retrieve the entire set of Games associated with a particular Country by using the table of Users.

Tables are defined in this way:

country
 id - integer
 name - string
 
 user
 id - integer
 country_id - integer
 name - string
 
 games
 id - integer
 user_id - integer
 title - string

Then you need to establish the Eloquent model of every table you have:

hasMany(User::class);
 
 
 public function games()
 
 return $this->hasManyThrough(Games::class, User::class);
 
 
belongsTo(Country::class);
 
 
 public function posts()
 
 return $this->hasMany(Post::class);
 
 
belongsTo(User::class);
 
 

Then we are able to call the game()method of the Country model to access every game since we have established that the "Has Many Through" relationship between Country and Game through the User model.

games;

Many-to-Many Relationship

The relationship between many and many is more complex. A good illustration is an employee that has many roles. A role can also be shared with multiple employees. This is what creates the multiple-to-many relation.

In order to do this, we have to include employee, roles, and rolestables.

Our database table structure should look something like:

employees
 id - integer
 name - string
 
 roles 
 id - integer
 name - string
 
 role_employees
 user_id - integer
 role_id - integer

With the tables structure of the relationship it is easy to determine our Employee Model in the belongToMany Role model.

belongsToMany(Role::class);
 
 

Once we defined this and then we have access to every role of an employee and even filter these roles to:

$employee = Employee::find(1);
 $employee->roles->forEach(function($role)  // );
 
 // OR 
 
 $employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();

Similar to other methods, we can overwrite both the local and foreign keys using the belongsToMany method.

To define the inverse relation of belongsToMany you can apply the same procedure, but on the child method now and using parents as arguments.

belongsToMany(Employee::class);
 
 

The use for The Intermediate Table

We may have spotted, when we use the many-tos-many relationship, we are always supposed to use an intermediary table. In this instance, we are using the job_employees table.

By default, the pivot table we have will include just the ID attributes. If we want more attributes, we will need add them in the following manner:

return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");

If we wish to shorten the pivot for timestamps, then we could do:

return $this->belongsToMany(Employee::class)->withTimestamps();

Another thing to be aware of is that we can customize the name "pivot" to whatever we want to suit our needs more effectively:

return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");
return $this->belongsToMany(Employee::class)->wherePivot('promoted', 1);
 return $this->belongsToMany(Employee::class)->wherePivotIn('level', [1, 2]);
 return $this->belongsToMany(Employee::class)->wherePivotNotIn('level', [2, 3]);
 return $this->belongsToMany(Employee::class)->wherePivotBetween('posted_at', ['2023-01-01 00:00:00', '2023-01-02 00:00:00']);
 return $this->belongsToMany(Employee::class)->wherePivotNull('expired_at');
 return $this->belongsToMany(Employee::class)->wherePivotNotNull('posted_at');

Another amazing thing is the ability to order through pivots:

return $this->belongsToMany(Employee::class)
 ->where('promoted', true)
 ->orderByPivot('hired_at', 'desc');

Polymorphic Relations

The term Polymorphic originates from Greek which means "many kinds of." This means that an application's model could take many types, which implies that it may have more than one association. Consider that we're creating an application with blogs, videos and polls. Users can make comments for any one of these. Therefore the model for a comment could be part of blogs, Videos, as well as Polls models.

Polymorphic One To One

This type of relationship is similar to a normal one-toone relationship. The only difference is that the child model could be linked to multiple types of model with one association.

Take, for example, a Tenantand Landlord model. It may have a polymorphic relationship with the WaterBill model.

The table structure can be like this:

tenants
 id - integer
 name - string
 
 landlords
 id - integer
 name - string
 
 waterbills
 id - integer
 amount - double
 waterbillable_id
 waterbillable_type

The waterbillable_id column is used to represent identification of the landlordor tenant,while the waterbillable_type contains the class name of the parent model. The type column is utilized in a way that is eloquent, to determine what parent model to return.

The definition model for such a relationship will look like:

morphTo();
 
 
 
 class Tenant extends Model
 
 public function waterBill() 
 
 return $this->morphOne(WaterBill::class, 'billable');
 
 
 
 class Landlord extends Model
 
 public function waterBill() 
 
 return $this->morphOne(WaterBill::class, 'billable');
 
 

When we've got all this set up We can then gain access to the information from both the Tenant and Landlord models:

waterBill;
 $landlord = Landlord::find(1)->waterBill;

Polymorphic One To Many

Similar to the regular one-to-many relationship The only different is that a child model may belong to multiple type of a model, using a single association.

On a platform like Facebook, users can comment on videos, posts and polls live etc. With a polymorphic one to several, we can create one table for comments table to keep the comments for all the kinds of categories that we can have. Our tables structure would look something like this:

posts 
 id - integer
 title - string
 body - text
 
 videos
 id - integer
 title - string
 url - string
 
 polls
 id - integer
 title - string
 
 comments 
 id - integer
 body - text
 commentable_id - integer
 commentable_type - string

The commentable_id being the id of the record, with the commentable_type is the class type, so eloquent knows what to look for. As for the model structure It is very identical to the polymorphic ones-to-many:

morphTo();
 
 
 
 class Poll extends Model
 
 public function comments()
 
 return $this->morphMany(Comment::class, 'commentable');
 
 
 
 class Live extends Model
 
 public function comments()
 
 return $this->morphMany(Comments::class, 'commentable');
 
 

Now to retrieve the comments of the Live live stream, simply use the Find method using the id, and now we have access to the comment iterable class:

comments as $comment)  
 
 // OR
 
 Live::find(1)->comments()->each(function($comment)  // );
 Live::find(1)->comments()->map(function($comment)  // );
 Live::find(1)->comments()->filter(function($comment)  // );
 
 // etc.

If we own the comments and would like to know who it is, we can access the commentable method:

commentable;
// commentable kind of post, Video, Poll, Live

Polymorphic one of Many

In a lot of applications that are large, we require an efficient way of communicating with models and between the models. There may be a need for a user's initial or final post that can be achieved using a mix of the morphOne as well as ofMany techniques:

morphOne(Post::class, 'postable')->latestOfMany();
 
 
 public function oldestPost()
 
 return $this->morphOne(Post::class, 'postable')->oldestOfMany();
 

The two methods latestOfMany and olderOfManyare getting the most current or the earliest model that is by relying on the model's initial key, which is the requirement that it was sortable.

Sometimes, we do not want to sort them according to ID, maybe we altered the date for publication of certain posts, and we need them in the order they were published, not by their id.

This can be done by passing two parameters to ofMany. ofMany method to help with this. The first element is the primary for us to filter on The second parameter is the sorting technique:

morphOne(Post::class, "postable")->ofMany("published_at", "max");
 

With this in mind, you can create further advanced relationships to this! Let's imagine this situation. We are asked to generate an inventory of the current articles in the order they have been published. This is a problem when we've got two posts that have the same value for published_at and also when the posts are scheduled for publication in the future.

In order to do this it is possible to pass the order in which we want the filters to be applied to using the ofMany method. This way we order by published_at and, if they're the same the order is based on ID. Additionally, we could apply a query function to this ofMany method in order to block all posts that are due for publication!

hasOne(Post::class)->ofMany([
 'published_at' => 'max',
 'id' => 'max',
 ], function ($query) 
 $query->where('published_at', '

Polymorphic Many To Many

The polymorphic many-to-many model is a little more intricate than the regular one. A common scenario is that tags that apply to multiple objects in your app. As an example, on TikTok, we have tags that can be applied to shorts, videos stories, and other types of content.

The polymorphic many-to-many allows us to create one table of tags for the videos, Shorts, and Stories.

Table structure is very simple:

videos
 id - integer
 description - string
 
 stories 
 id - integer
 description - string
 
 taggables 
 tag_id - integer
 taggable_id - integer
 taggable_type - string

With the tables ready, we can make the model and use the morphToMany method. This method will accept the name of the model class as well as the name of the relationship':

morphToMany(Tag::class, 'taggable');
 
 

And with this it is easy to define the inverse relation. For each model that has children, we should refer to as"morphedByMany". morphedByMany procedure.

morphedByMany(Story::class, 'taggable');
 
 
 public function videos()
 
 return $this->morphedByMany(Video::class, 'taggable');
  
 

When we receive an Tag that we want to use, we are able to retrieve all videos and stories connected to the tag!

stories;
 $videos = $tag->stories;

Improve Eloquent for speed

Laravel provides a flexible caching system that works with a variety of backends like Redis, Memcached, and file-based caching. Through caching the results of Eloquent queries it can decrease the volume of queries to databases and make your application more efficient and more valuable.

Additionally, you can use Laravel's queries builder to create additional complex queries to further enhance the speed of your app.

Summary

In sum, Eloquent relations are a powerful characteristic of Laravel that lets developers quickly work with data related to each other. From one-to-one to many-to-many relationships Eloquent offers a straightforward and intuitive syntax to define and analyze these relationships.