Eloquent: Relationships
Introduction
Database tables are often related to one another. For example, a blog post may have many comments or an order could be related to the user who placed it. Eloquent makes managing and working with these relationships easy, and supports a variety of common relationships:
- One To One
- One To Many
- Many To Many
- Has One Through
- Has Many Through
- One To One (Polymorphic)
- One To Many (Polymorphic)
- Many To Many (Polymorphic)
Defining Relationships
Eloquent relationships are defined as methods on your Eloquent model classes. Since relationships also serve as powerful query builders, defining relationships as methods provides powerful method chaining and querying capabilities. For example, we may chain additional query constraints on this posts
relationship:
$user->posts()->where('active', 1)->get();
But, before diving too deep into using relationships, let's learn how to define each type of relationship supported by Eloquent.
One to One
A one-to-one relationship is a very basic type of database relationship. For example, a User
model might be associated with one Phone
model. To define this relationship, we will place a phone
method on the User
model. The phone
method should call the hasOne
method and return its result. The hasOne
method is available to your model via the model's Illuminate\Database\Eloquent\Model
base class:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class User extends Model
{
/**
* Get the phone associated with the user.
*/
public function phone(): HasOne
{
return $this->hasOne(Phone::class);
}
}
The first argument passed to the hasOne
method is the name of the related model class. Once the relationship is defined, we may retrieve the related record using Eloquent's dynamic properties. Dynamic properties allow you to access relationship methods as if they were properties defined on the model:
$phone = User::find(1)->phone;
Eloquent determines the foreign key of the relationship based on the parent model name. In this case, the Phone
model is automatically assumed to have a user_id
foreign key. If you wish to override this convention, you may pass a second argument to the hasOne
method:
return $this->hasOne(Phone::class, 'foreign_key');
Additionally, Eloquent assumes that the foreign key should have a value matching the primary key column of the parent. In other words, Eloquent will look for the value of the user's id
column in the user_id
column of the Phone
record. If you would like the relationship to use a primary key value other than id
or your model's $primaryKey
property, you may pass a third argument to the hasOne
method:
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
Defining the Inverse of the Relationship
So, we can access the Phone
model from our User
model. Next, let's define a relationship on the Phone
model that will let us access the user that owns the phone. We can define the inverse of a hasOne
relationship using the belongsTo
method:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
When invoking the user
method, Eloquent will attempt to find a User
model that has an id
which matches the user_id
column on the Phone
model.
Eloquent determines the foreign key name by examining the name of the relationship method and suffixing the method name with _id
. So, in this case, Eloquent assumes that the Phone
model has a user_id
column. However, if the foreign key on the Phone
model is not user_id
, you may pass a custom key name as the second argument to the belongsTo
method:
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key');
}
If the parent model does not use id
as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the belongsTo
method specifying the parent table's custom key:
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
One to Many
A one-to-many relationship is used to define relationships where a single model is the parent to one or more child models. For example, a blog post may have an infinite number of comments. Like all other Eloquent relationships, one-to-many relationships are defined by defining a method on your Eloquent model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
Remember, Eloquent will automatically determine the proper foreign key column for the Comment
model. By convention, Eloquent will take the "snake case" name of the parent model and suffix it with _id
. So, in this example, Eloquent will assume the foreign key column on the Comment
model is post_id
.
Once the relationship method has been defined, we can access the collection of related comments by accessing the comments
property. Remember, since Eloquent provides "dynamic relationship properties", we can access relationship methods as if they were defined as properties on the model:
use App\Models\Post;
$comments = Post::find(1)->comments;
foreach ($comments as $comment) {
// ...
}
Since all relationships also serve as query builders, you may add further constraints to the relationship query by calling the comments
method and continuing to chain conditions onto the query:
$comment = Post::find(1)->comments()
->where('title', 'foo')
->first();
Like the hasOne
method, you may also override the foreign and local keys by passing additional arguments to the hasMany
method:
return $this->hasMany(Comment::class, 'foreign_key');
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
One to Many (Inverse) / Belongs To
Now that we can access all of a post's comments, let's define a relationship to allow a comment to access its parent post. To define the inverse of a hasMany
relationship, define a relationship method on the child model which calls the belongsTo
method:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
Once the relationship has been defined, we can retrieve a comment's parent post by accessing the post
"dynamic relationship property":
use App\Models\Comment;
$comment = Comment::find(1);
return $comment->post->title;
In the example above, Eloquent will attempt to find a Post
model that has an id
which matches the post_id
column on the Comment
model.
Eloquent determines the default foreign key name by examining the name of the relationship method and suffixing the method name with a _
followed by the name of the parent model's primary key column. So, in this example, Eloquent will assume the Post
model's foreign key on the comments
table is post_id
.
However, if the foreign key for your relationship does not follow these conventions, you may pass a custom foreign key name as the second argument to the belongsTo
method:
/**
* Get the post that owns the comment.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key');
}
If your parent model does not use id
as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the belongsTo
method specifying your parent table's custom key:
/**
* Get the post that owns the comment.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}
Default Models
The belongsTo
, hasOne
, hasOneThrough
, and morphOne
relationships allow you to define a default model that will be returned if the given relationship is null
. This pattern is often referred to as the Null Object pattern and can help remove conditional checks in your code. In the following example, the user
relation will return an empty App\Models\User
model if no user is attached to the Post
model:
/**
* Get the author of the post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault();
}
To populate the default model with attributes, you may pass an array or closure to the withDefault
method:
/**
* Get the author of the post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
/**
* Get the author of the post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
$user->name = 'Guest Author';
});
}
Querying Belongs To Relationships
When querying for the children of a "belongs to" relationship, you may manually build the where
clause to retrieve the corresponding Eloquent models:
use App\Models\Post;
$posts = Post::where('user_id', $user->id)->get();
However, you may find it more convenient to use the whereBelongsTo
method, which will automatically determine the proper relationship and foreign key for the given model:
$posts = Post::whereBelongsTo($user)->get();
You may also provide a collection instance to the whereBelongsTo
method. When doing so, Laravel will retrieve models that belong to any of the parent models within the collection:
$users = User::where('vip', true)->get();
$posts = Post::whereBelongsTo($users)->get();
By default, Laravel will determine the relationship associated with the given model based on the class name of the model; however, you may specify the relationship name manually by providing it as the second argument to the whereBelongsTo
method:
$posts = Post::whereBelongsTo($user, 'author')->get();
Has One of Many
Sometimes a model may have many related models, yet you want to easily retrieve the "latest" or "oldest" related model of the relationship. For example, a User
model may be related to many Order
models, but you want to define a convenient way to interact with the most recent order the user has placed. You may accomplish this using the hasOne
relationship type combined with the ofMany
methods:
/**
* Get the user's most recent order.
*/
public function latestOrder(): HasOne
{
return $this->hasOne(Order::class)->latestOfMany();
}
Likewise, you may define a method to retrieve the "oldest", or first, related model of a relationship:
/**
* Get the user's oldest order.
*/
public function oldestOrder(): HasOne
{
return $this->hasOne(Order::class)->oldestOfMany();
}
By default, the latestOfMany
and oldestOfMany
methods will retrieve the latest or oldest related model based on the model's primary key, which must be sortable. However, sometimes you may wish to retrieve a single model from a larger relationship using a different sorting criteria.
For example, using the ofMany
method, you may retrieve the user's most expensive order. The ofMany
method accepts the sortable column as its first argument and which aggregate function (min
or max
) to apply when querying for the related model:
/**
* Get the user's largest order.
*/
public function largestOrder(): HasOne
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}
Because PostgreSQL does not support executing the MAX
function against UUID columns, it is not currently possible to use one-of-many relationships in combination with PostgreSQL UUID columns.
Converting "Many" Relationships to Has One Relationships
Often, when retrieving a single model using the latestOfMany
, oldestOfMany
, or ofMany
methods, you already have a "has many" relationship defined for the same model. For convenience, Laravel allows you to easily convert this relationship into a "has one" relationship by invoking the one
method on the relationship:
/**
* Get the user's orders.
*/
public function orders(): HasMany
{
return $this->hasMany(Order::class);
}
/**
* Get the user's largest order.
*/
public function largestOrder(): HasOne
{
return $this->orders()->one()->ofMany('price', 'max');
}
Advanced Has One of Many Relationships
It is possible to construct more advanced "has one of many" relationships. For example, a Product
model may have many associated Price
models that are retained in the system even after new pricing is published. In addition, new pricing data for the product may be able to be published in advance to take effect at a future date via a published_at
column.
So, in summary, we need to retrieve the latest published pricing where the published date is not in the future. In addition, if two prices have the same published date, we will prefer the price with the greatest ID. To accomplish this, we must pass an array to the ofMany
method that contains the sortable columns which determine the latest price. In addition, a closure will be provided as the second argument to the ofMany
method. This closure will be responsible for adding additional publish date constraints to the relationship query:
/**
* Get the current pricing for the product.
*/
public function currentPricing(): HasOne
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function (Builder $query) {
$query->where('published_at', '<', now());
});
}
Has One Through
The "has-one-through" relationship defines a one-to-one relationship with another model. However, this relationship indicates that the declaring model can be matched with one instance of another model by proceeding through a third model.
For example, in a vehicle repair shop application, each Mechanic
model may be associated with one Car
model, and each Car
model may be associated with one Owner
model. While the mechanic and the owner have no direct relationship within the database, the mechanic can access the owner through the Car
model. Let's look at the tables necessary to define this relationship:
mechanics
id - integer
name - string
cars
id - integer
model - string
mechanic_id - integer
owners
id - integer
name - string
car_id - integer
Now that we have examined the table structure for the relationship, let's define the relationship on the Mechanic
model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}
The first argument passed to the hasOneThrough
method is the name of the final model we wish to access, while the second argument is the name of the intermediate model.
Or, if the relevant relationships have already been defined on all of the models involved in the relationship, you may fluently define a "has-one-through" relationship by invoking the through
method and supplying the names of those relationships. For example, if the Mechanic
model has a cars
relationship and the Car
model has an owner
relationship, you may define a "has-one-through" relationship connecting the mechanic and the owner like so:
// String based syntax...
return $this->through('cars')->has('owner');
// Dynamic syntax...
return $this->throughCars()->hasOwner();