w3resource

Laravel (5.7) Eloquent Relationships

Introduction

In an application there is often a relationship between the database tables. For instance, a blog post may have many views and reacts, an order can be related to the customer who placed t. Eloquent make the management of relationships in our application very easy.

Defining Relationships

We define eloquent relationships as methods on our Eloquent model classes. Just like Eloquent models themselves, Eloquent relationships also serve as powerful query builders, and defining relationships as methods provides powerful method chaining and querying capabilities. For instance, we may chain additional constraints on this posts relationship:

$user->posts()->where('active', 1)->get();

Before we dive too deep into using relationship, let us learn how to define each type.

One To One

The one-to-one relationship is a very basic relation. For instance, a User model may be associated with one Phone. For us to define this relationship, we have to place a phone method on the User model. Then the phone method should call the hasOne method and return its result:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the phone record associated with the user.
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

The first argument that is passed to the hasOne method is the name of the related model. Once the relationship has been defined, we can then retrieve the related record using Eloquent's dynamic properties. Dynamic properties will allow us to access relationship methods as if they were properties defined on the model:

$phone = User::find(1)->phone;

The Eloquent determines the foreign key of the relationship based on the model name. In cases as this, the Phone model will be automatically assumed to have a user_id foreign key. If we wish to override this convention, we may pass a second argument to the hasOne method:

return $this->hasOne('App\Phone', 'foreign_key');

In addition, Eloquent assumes that the foreign key has a value matching the id (or the custom $primaryKey) column of the parent. This means that, Eloquent will look for the value of the user's id column in the user_id column of the Phone record. If we would like the relationship to use a value other than id, we may pass a third argument to the hasOne method specifying our custom key:

return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

Defining The Inverse Of The Relationship

So, we have access the Phone model from our User. Now, let us 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 y busing the belongsTo method:

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model
{
    /**
     * Get the user that owns the phone.
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

In the above example, Eloquent will attempt to match the user_id from the phone to an id on the User model. Eloquent will determine the default foreign key name by examining the name of the relationship method and suffixing the method name with _id. But, in the case where the foreign key on the phone model is not user_id, you can pass a custom key name as the second argument to the belongsTo method:

public function user()
{
    return $this->belongsTo('App\User', 'foreign_key');
}

if our parent model does not use id as its primary key, or we wish to join the model to a different column, we may pass a third argument to the belongsTo method by specifying our parent table?s custom key:

/**
 * Get the user that owns the phone.
 */
public function user()
{
    return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}

One To Many

A one-to-many relationship is used when we want to define relationships where a single model owns any amount of other models. For instance, a blog post could have an infinite number of comments. Just like all other Eloquent relationships, one-to-many relationships are defined when we place a function on our Eloquent model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Get the comments for the blog post.
     */
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

Recall, Eloquent automatically determines the proper foreign key column on the Comment model. As a convention, Eloquent takes the "snake case" name of the owning model and suffixes it with _id. So, in this example, Eloquent will assume the foreign key on the Comment model is post_id.

Once we have defined the relationship, we can access the collection of comments by accessing the comments property. Recall that, because Eloquent provides "dynamic properties", we can also access relationship methods as if they were defined as properties on the model:

$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
    //
}

Since all the relationships also serve as query builders, we can add further constraints to which comments are retrieved by calling the comments method and then continuing to chain conditions onto the query:

$comment = App\Post::find(1)->comments()->where('title', 'foo')->first();

Just like the hasOne method, we can also override the foreign and the local keys by passing additional arguments to the hasMany method:

return $this->hasMany('App\Comment', 'foreign_key');

return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

One To Many (Inverse)

Now that we can access all of a post's comments, let us define a relationship to allow a comment to access its parent post. To define the inverse of a hasMany relationship, we define a relationship function on the child model which calls the belongsTo method:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get the post that owns a comment.
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

Once the relationship is defined, we can retrieve the Post model for a particular Comment by accessing the post "dynamic property":

$comment = App\Comment::find(1);
echo $comment->post->title;

In the above example, Eloquent will try to match the post_id from the Comment model to an id on the Post model. Eloquent will determine the default foreign key name by examining the name of the relationship method and by suffixing the method name with a _ followed by the name of the primary key column. However, if the foreign key on the Comment model is not post_id, you may pass a custom key name as the second argument to the belongsTo method:

/**
 * Get the post that owns the comment.
 */
public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key');
}

If our parent model does not use id as its primary key, or we wish to join the child model to a different column, we may pass a third argument to the belongsTo method specifying our parent table's custom key:

/**
 * Get the post that owns the comment.
 */
public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}

Many To Many

The many-to-many relations are slightly more complicated than hasOne and hasMany relationships. A typical example of such a relationship is a user with many roles, where the roles are shared by other users as well. For instance, more than one user may have the role of "Admin". In defining this relationship, we need three database tables: users, roles, and role_user. The role_user table will be derived from the alphabetical order of the related model names, and will contain the user_id and role_id columns.

We define many-to-many relationships by writing a method that returns the result of the belongsToMany method. For example, let us define the roles method on our User model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}

Once the relationship has been defined, you can access the user's roles using the roles dynamic property:

$user = App\User::find(1);

foreach ($user->roles as $role) {
    //
}

Just like all other relationship types, you can call the roles method to continue chaining query constraints onto the relationship:

$roles = App\User::find(1)->roles()->orderBy('name')->get();

As we mentioned previously, to determine the table name of the relationship's joining table, Eloquent joins the two related model names in alphabetical order. However, you can override this convention. You can do so by passing a second argument to the belongsToMany method:

return $this->belongsToMany('App\Role', 'role_user');

Added to customizing the name of the joining table, you can also customize the column names of the keys on the table by passing additional arguments to the belongsToMany method. The third argument will be the foreign key name of the model on which you are defining the relationship, while the fourth argument will be the foreign key name of the model that you are joining to:

return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');

Defining The Inverse Of The Relationship

In defining the inverse of a many-to-many relationship, you place another call to belongsToMany on your related model. To continue our user roles example, let's define the users method on the Role model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User');
    }
}

You can see now that, the relationship has been defined exactly the same as its User counterpart, with an exception of referencing the App\User model. Since we are reusing the belongsToMany method, all the usual table and key customization options are available when you are defining the inverse of many-to-many relationships.

Retrieving Intermediate Table Columns

As you have learned already, working with many-to-many relations will require the presence of an intermediate table. Eloquent provides some very helpful ways of interacting with this table. For example, let us assume our User object has many Role objects that it is related to. After we access this relationship, we can access the intermediate table using the pivot attribute on the models:

$user = App\User::find(1);

foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

Notice that every Role model we retrieve is automatically assigned a pivot attribute. This attribute will contain a model representing the intermediate table, and can be used like any other Eloquent model.

By default, it is only the model keys that will be present on the pivot object. If our pivot table contains extra attributes, we must specify them when defining the relationship:

return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

If we want our pivot table to have automatically maintained created_at and updated_at timestamps, we have to use the withTimestamps method on the relationship definition:

return $this->belongsToMany('App\Role')->withTimestamps();

Customizing The pivot Attribute Name

As we noted earlier, attributes from the intermediate table can be accessed on models using the pivot attribute. However, we are free to customize the name of this attribute to better reflect its purpose within our application.

For instance, if our application contains users that may subscribe to podcasts, we probably have a many-to-many relationship between users and podcasts. If cases as this, we may wish to rename our intermediate table accessor to subscription instead of pivot. This can be done using the as method when defining the relationship:

return $this->belongsToMany('App\Podcast')
                ->as('subscription')
                ->withTimestamps();

Once this has been done, we may access the intermediate table data using the customized name:

$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
    echo $podcast->subscription->created_at;
}

Filtering Relationships Via Intermediate Table Columns

We can also filter the results returned by belongsToMany using the wherePivotIn and wherePivot methods when defining the relationship:

return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);

Defining Custom Intermediate Table Models

If you would love to define a custom model to represent the intermediate table of your relationship, you can call the using method when defining the relationship. Custom many-to-many pivot models will have to extend the Illuminate\Database\Eloquent\Relations\Pivot class while custom polymorphic many-to-many pivot models should extend the Illuminate\Database\Eloquent\Relations\MorphPivot class. For instance, you may define a Role which uses a custom RoleUser pivot model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User')->using('App\RoleUser');
    }
}

When defining the RoleUser model, we will extend the Pivot class:

<?php

namespace App;

use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    //
}

You can combine withPivot and using in order to retrieve columns from the intermediate table. For instance, you can retrieve the created_by and updated_by columns from the RoleUser pivot table by passing the column names to the withPivot method:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users()
    {
        return $this->belongsToMany('App\User')
                        ->using('App\RoleUser')
                        ->withPivot([
                            'created_by',
                            'updated_by'
                        ]);
    }
}

Custom Pivot Models And Incrementing IDs

If you defined a many-to-many relationship that uses a custom pivot model, and the pivot model has an auto-incrementing primary key, then you should ensure your custom pivot model class defines an incrementing property that is set to true.

/**
 * Indicates if the IDs are auto-incrementing.
 *
 * @var bool
 */
public $incrementing = true;

Has One Through

"has-one-through" relationships link models through a single intermediate relation. For instance, if every supplier has one user, and every user is associated with one user history record, then the supplier model may access the user's history through the user. Let us look at the database tables necessary to define this relationship:

users
    id - integer
    supplier_id - integer

suppliers
    id - integer

history
    id - integer
    user_id ? integer

although the history table does not contain a supplier_id column, the hasOneThrough relation provides access to the user's history to the supplier model. Now that we have successfully examined the table structure for the relationship, let us define it on the Supplier model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Supplier extends Model
{
    /**
     * Get the user's history.
     */
    public function userHistory()
    {
        return $this->hasOneThrough('App\History', 'App\User');
    }
}

The first argument that is passed to the hasOneThrough method is the name of the final model we wish to access, and the second argument is the name of the intermediate model.

Typical Eloquent foreign key conventions are used when performing the relationship's queries. If we would like to customize the keys of the relationship, we may pass them as the third and fourth arguments to the hasOneThrough method. while third argument is the name of the foreign key on the intermediate model. The fourth argument will be the name of the foreign key on the final model. The fifth argument will the local key, while the sixth argument will be the local key of the intermediate model:

class Supplier extends Model
{
    /**
     * Get the user's history.
     */
    public function userHistory()
    {
        return $this->hasOneThrough(
            'App\History',
            'App\User',
            'supplier_id', // Foreign key on users table...
            'user_id', // Foreign key on history table...
            'id', // Local key on suppliers table...
            'id' // Local key on users table...
        );
    }
}

Has Many Through

The "has-many-through" relationship will provide a convenient shortcut for accessing distant relations via an intermediate relation. For instance, a Country model could have many Post models through an intermediate User model. In this instance, we could easily gather all blog posts for a given country. Let us look at the tables required to define this relationship:

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title ? string

Though posts table does not contain a country_id column, the hasManyThrough relation will provide access to a country's posts via $country->posts. For this query to be performed, Eloquent will inspect the country_id on the intermediate users table. After it find the matching user IDs, they will then be used to query the posts table.

ince we have examined the table structure for the relationship, let us define it on the Country model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    /**
     * Get all of the posts for the country.
     */
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}

The first argument that is passed to the hasManyThrough method is the name of the final model we wish to access, while the second argument that is passed is the name of the intermediate model.

Typical Eloquent foreign key conventions are used when performing the relationship's queries. If we would like to customize the keys of the relationship, we can pass them as the third and fourth arguments to the hasManyThrough method. The third argument will be the name of the foreign key on the intermediate model. The fourth argument that is passed is the name of the foreign key on the final model. The fifth argument will be the local key, and the sixth argument is the local key of the intermediate model:

class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            'App\Post',
            'App\User',
            'country_id', // Foreign key on users table...
            'user_id', // Foreign key on posts table...
            'id', // Local key on countries table...
            'id' // Local key on users table...
        );
    }
}

Polymorphic Relationships>

A polymorphic relationship will allow the target model to belong to more than one type of model using a single association.

One To One (Polymorphic)

Table Structure

The one-to-one polymorphic relation is similar to a simple one-to-one relation; however, the target model may belong to more than one type of model on a single association. For instance, a blog Post and a User might share a polymorphic relation to an Image model. Using a one-to-one polymorphic relation will allow us to have a single list of unique images that are used for both blog posts and user accounts. First, let us examine the table structure:

posts
    id - integer
    name - string

users
    id - integer
    name - string

images
    id - integer
    url - string
    imageable_id - integer
    imageable_type ? string

Notice the imageable_type and imageable_id columns on the images table. The imageable_id column contains the ID value of the post or user, while the imageable_type column contains the class name of the parent model. The imageable_type column is used by Eloquent in determining which "type" of parent model to return when accessing the imageable relation.

Model Structure

Next, let us examine the model definitions needed to build this relationship:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Image extends Model
{
    /**
     * Get the owning imageable model.
     */
    public function imageable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get the post's image.
     */
    public function image()
    {
        return $this->morphOne('App\Image', 'imageable');
    }
}

class User extends Model
{
    /**
     * Get the user's image.
     */
    public function image()
    {
        return $this->morphOne('App\Image', 'imageable');
    }
}

Retrieving The Relationship

Once our database table and our models are defined, we may access the relationships via our models. For instancing, in retrieving the image for a post, we can make use of the image dynamic property:

$post = App\Post::find(1);

$image = $post->image;

we may also retrieve the parent from the polymorphic model by accessing the name of the method that performs the call to morphTo. In our case, this is the imageable method on the Image model. So, we can still access that method as a dynamic property:

$image = App\Image::find(1);
$imageable = $image->imageable;

The imageable relation on the Image model returns either a Post or User instance, depending on which type of model that owns the image.

One To Many (Polymorphic)

Table Structure

The one-to-many polymorphic relation is similar to a simple one-to-many relation; however, the target model could belong to more than one type of model on a single association. For instance, let?s imagine users of your application can "comment" on both posts and videos. If we use polymorphic relationships, we may use a single comments table for both of these scenarios. First, let us examine the table structure required to build this relationship:

posts
    id - integer
    title - string
    body - text

videos
    id - integer
    title - string
    url - string

comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type ? string

Model Structure

Next, let us examine the model definitions needed to build this relationship:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get the owning commentable model.
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

Retrieving The Relationship

Once our database table and models are defined, we may access the relationships via our models. For instance, if we want to access all of the comments for a post, we can use the dynamic property of the comments:

$post = App\Post::find(1);

foreach ($post->comments as $comment) {
    //
}

We can also retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the method that performs the call to morphTo. In our case, that is the commentable method on the Comment model. So, we will be able to access that method as a dynamic property:

$comment = App\Comment::find(1);
$commentable = $comment->commentable;

The commentable relation on the Comment model will thus return either a Post or Video instance, depending on which type of model owns the comment.

Many To Many (Polymorphic)

Table Structure

The many-to-many polymorphic relations are slightly more complicated than morphMany and morphOne relationships. For instance, a given blog Post and Video model could share a polymorphic relation to a Tag model. Using a many-to-many polymorphic relation will allow you to have a single list of unique tags that are shared across blog posts and videos. First, let us examine the table structure:

posts
    id - integer
    name - string

videos
    id - integer
    name - string

tags
    id - integer
    name - string

taggables
    tag_id - integer
    taggable_id - integer
    taggable_type ? string

Model Structure

Next, we are ready to define the relationships on the model. The Video and Post models will both have a tags method that calls the morphToMany method on the base Eloquent class:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Get all of the tags for the post.
     */
    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }
}

Defining The Inverse Of The Relationship

Next, on the Tag model, you should define a method for each of its related models. So, for this example, we will define a posts method and a videos method:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    /**
     * Get all of the posts that are assigned this tag.
     */
    public function posts()
    {
        return $this->morphedByMany('App\Post', 'taggable');
    }

    /**
     * Get all of the videos that are assigned this tag.
     */
    public function videos()
    {
        return $this->morphedByMany('App\Video', 'taggable');
    }
}

Retrieving The Relationship

when our database table and models are defined, we may access the relationships via our models. For instance, to access all of the tags for a post, we can use the tags dynamic property:

$post = App\Post::find(1);

foreach ($post->tags as $tag) {
    //
}

You can also retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the method that performs the call to morphedByMany. In our case scenario, that will be the posts or videos methods on the Tag model. So, you will be able to access those methods as dynamic properties:

$tag = App\Tag::find(1);

foreach ($tag->videos as $video) {
    //
}

Custom Polymorphic Types

Laravel by default, will use the fully qualified class name to store the type of the related model. For example, given the one-to-many instance above where a Comment may belong to a Video or a Post, the default commentable_type would be either App\Video or App\Post, respectively. However, we may wish to decouple our database from our application's internal structure. In such a case, we may define a "morph map" to instruct Eloquent to use a custom name for each model instead of the class name:

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    'posts' => 'App\Post',
    'videos' => 'App\Video',
]);

You can register the morphMap in the boot function of your AppServiceProvider or you create a separate service provider if you wish.

Querying Relations

Since we now know that all types of Eloquent relationships are defined via methods, we may call those methods to obtain an instance of the relationship without actually executing the relationship queries. Additionally, all types of Eloquent relationships also serve as query builders, allowing us to continue to chain constraints onto the relationship query before finally executing the SQL against our database.

For instance, think of a blog system in which a User model has many associated Post models:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get all of the posts for the user.
     */
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

You can query the posts relationship and also add additional constraints to the relationship like so:

$user = App\User::find(1);

$user->posts()->where('active', 1)->get();

You are also able to use any of the query builder methods on the relationship.

Chaining orWhere Clauses After Relationships

As demonstrated in the instance above, we are free to add additional constraints to relationships when querying them. However, caution is needed when chaining orWhere clauses onto a relationship, as the orWhere clauses will be logically grouped at the same level as the relationship constraint:

$user->posts()
        ->where('active', 1)
        ->orWhere('votes', '>=', 100)
        ->get();

// select * from posts 
// where user_id =  and active = 1 or votes >= 100

Most times, you will likely intend to use constraint groups to logically group the conditional checks between parentheses:

use Illuminate\Database\Eloquent\Builder;

$user->posts()
        ->where(function (Builder $query) {
            return $query->where('active', 1)
                         ->orWhere('votes', '>=', 100);
        })
        ->get();

// select * from posts 
// where user_id = ? and (active = 1 or votes >= 100)

Relationship Methods Vs. Dynamic Properties

If you don?t need to add additional constraints to an Eloquent relationship query, you can access the relationship as if it were a property. For instance:

$user = App\User::find(1);

foreach ($user->posts as $post) {
    //
}

Dynamic properties are by default "lazy loading", this mean that they will only load their relationship data when you actually access them.

Querying Relationship Existence

When we are accessing the records for a model, we may wish to limit our results based on the existence of a relationship. To do so, you can pass the name of the relationship to the has and orHas methods:

// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();

You can also specify an operator and count to further customize the query:

// Retrieve all posts that have three or more comments...
$posts = App\Post::has('comments', '>=', 3)->get();

Nested has statements can also be constructed using "dot" notation. For instance, you can retrieve all posts that have at least one comment and vote:

`
// Retrieve posts that have at least one comment with votes...
$posts = App\Post::has('comments.votes')->get();

If you want even more power, you can use the whereHas and orWhereHas methods to put "where" conditions on your has queries. These methods will allow you to add customized constraints to a relationship constraint, like checking the content of a comment:

use Illuminate\Database\Eloquent\Builder;

// Retrieve posts with at least one comment containing words like foo%...
$posts = App\Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
})->get();

// Retrieve posts with at least ten comments containing words like foo%...
$posts = App\Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
}, '>=', 10)->get();

Querying Relationship Absence

When we are accessing the records for a model, you may wish to limit our results based on the absence of a relationship. To do this, we can pass the name of the relationship to the doesntHave and orDoesntHave methods:

$posts = App\Post::doesntHave('comments')->get();

If we need even more power, we can use the whereDoesntHave and orWhereDoesntHave methods to put "where" conditions on our doesntHave queries. These methods allow us to add customized constraints to a relationship constraint, like checking the content of a comment:

use Illuminate\Database\Eloquent\Builder;

$posts = App\Post::whereDoesntHave('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
})->get();

we may use "dot" notation to execute a query against a nested relationship. For instance, the following query will be able to retrieve all posts with comments from authors that are not banned:

use Illuminate\Database\Eloquent\Builder;

$posts = App\Post::whereDoesntHave('comments.author', function (Builder $query) {
    $query->where('banned', 1);
})->get();

Querying Polymorphic Relationships

For us to query the existence of MorphTo relationships, we may use the whereHasMorph method and its other corresponding methods:

use Illuminate\Database\Eloquent\Builder;

// Retrieve comments associated to posts or videos with a title like foo%...
$comments = App\Comment::whereHasMorph(
    'commentable', 
    ['App\Post', 'App\Video'], 
    function (Builder $query) {
        $query->where('title', 'like', 'foo%');
    }
)->get();

// Retrieve comments associated to posts with a title not like foo%...
$comments = App\Comment::whereDoesntHaveMorph(
    'commentable', 
    'App\Post', 
    function (Builder $query) {

        $query->where('title', 'like', 'foo%');
    }
)->get();

We can use the $type parameter to add different constraints depending on the related model:

use Illuminate\Database\Eloquent\Builder;

$comments = App\Comment::whereHasMorph(
    'commentable', 
    ['App\Post', 'App\Video'], 
    function (Builder $query, $type) {
        $query->where('title', 'like', 'foo%');

        if ($type === 'App\Post') {
            $query->orWhere('content', 'like', 'foo%');
        }
    }
)->get();

Rather than passing an array of possible polymorphic models, we can provide * as a wildcard and allow Laravel retrieve all the possible polymorphic types from the database. Laravel will then execute an additional query in order to perform this operation:


```use Illuminate\Database\Eloquent\Builder;

$comments = App\Comment::whereHasMorph('commentable', '*', function (Builder $query) {
    $query->where('title', 'like', 'foo%');
})->get();

Counting Related Models

If you need to count the number of results from a relationship without actually loading them you may use the withCount method, and then this will place a {relation}_count column on your resulting models. For example:

$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
    echo $post->comments_count;
}

You can add the "counts" for multiple relations as well as add constraints to the queries:

use Illuminate\Database\Eloquent\Builder;

$posts = App\Post::withCount(['votes', 'comments' => function (Builder $query) {
    $query->where('content', 'like', 'foo%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

You can also alias the relationship count result, thus allowing multiple counts on the same relationship:

use Illuminate\Database\Eloquent\Builder;

$posts = App\Post::withCount([
    'comments',
    'comments as pending_comments_count' => function (Builder $query) {
        $query->where('approved', false);
    }
])->get();

echo $posts[0]->comments_count;

echo $posts[0]->pending_comments_count;

If you are combining withCount with a select statement, please ensure that you call withCount after the select method:

$posts = App\Post::select(['title', 'body'])->withCount('comments')->get();

echo $posts[0]->title;
echo $posts[0]->body;
echo $posts[0]->comments_count;

Eager Loading

When we are accessing Eloquent relationships as properties, the relationship data will be "lazy loaded". This means that the relationship data is not actually loaded until you first access the property. However, Eloquent can equally "eager load" relationships at the time you query the parent model. Eager loading will alleviate the N + 1 query problem. For example:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}

Now, let us retrieve all books and their authors:

$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

This loop will execute 1 query to retrieve all the books on the table, and then another query for each book to retrieve the author. So, if we have 25 books on the table, this loop would have to run 26 queries: 1 query for the original book, and 25 additional queries to retrieve the author of every book.

we can actually use eager loading to reduce this operation to just 2 queries. When we query, we can specify which relationships should be eager loaded using the with method:

$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

In this operation, we will only execute two queries:

select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

Eager Loading Multiple Relationships

Sometimes you may want to eager load several different relationships in a single operation. To do this, you should just pass additional arguments to the with method:

$books = App\Book::with(['author', 'publisher'])->get();

Nested Eager Loading

For us to eager load nested relationships, we can use "dot" syntax. For instance, let us eager load all of the book's authors and all the author's personal contacts in a single Eloquent statement:

$books = App\Book::with('author.contacts')->get();

Nested Eager Loading morphTo Relationships

If you would want to eager load a morphTo relationship, as well as nested relationships on the various entities that can be returned by that relationship, you can use the with method in combination with the morphTo relationship's morphWith method. Let us consider the following model:

<?php

use Illuminate\Database\Eloquent\Model;

class ActivityFeed extends Model
{
    /**
     * Get the parent of the activity feed record.
     */
    public function parentable()
    {
        return $this->morphTo();
    }
}

In this instance, let us assume Event, Photo, and Post models can create ActivityFeed models. In addition, let us assume that Event models belong to a Calendar model, and that Photo models are associated with Tag models, and the Post models belong to an Author model.

Using these model relationships and definitions, we can retrieve ActivityFeed model instances and eager load all the parentable models as well as their respective nested relationships:

use Illuminate\Database\Eloquent\Relations\MorphTo;

$activities = ActivityFeed::query()
    ->with(['parentable' => function (MorphTo $morphTo) {
        $morphTo->morphWith([
            Event::class => ['calendar'],
            Photo::class => ['tags'],
            Post::class => ['author'],
        ]);
    }])->get();

Eager Loading Specific Columns

You can specify the column relationships you want to retrieve:

$books = App\Book::with('author:id,name')->get();

Eager Loading By Default

Sometimes we might want to always load some relationships when retrieving a model. For us to accomplish this, we may define a $with property on the model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    /**
     * The relationships that should always be loaded.
     *
     * @var array
     */
    protected $with = ['author'];

    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}

If you would love to remove an item from the $with property for a single query, you can use the without method:

$books = App\Book::without('author')->get();

Constraining Eager Loads

Sometimes we may wish to eager load a relationship, and equally specify additional query conditions for the eager loading query. Here is an example:

use Illuminate\Database\Eloquent\Builder;

$users = App\User::with(['posts' => function (Builder $query) {
    $query->where('title', 'like', '%first%');
}])->get();

In this instance, Eloquent only eager loads posts where the post's title column contains the word first. You can call other query builder methods to further customize the eager loading operation:

use Illuminate\Database\Eloquent\Builder;

$users = App\User::with(['posts' => function (Builder $query) {
    $query->orderBy('created_at', 'desc');
}])->get();

Lazy Eager Loading

Sometimes we may need to eager load a relationship after the parent model has already been retrieved:

$books = App\Book::all();

if ($someCondition) {
    $books->load('author', 'publisher');
}

If we need to set additional query constraints on the eager loading query, we can pass an array keyed by the relationships we wish to load. The array values should then be Closure instances which receive the query instance:

use Illuminate\Database\Eloquent\Builder;

$books->load(['author' => function (Builder $query) {
    $query->orderBy('published_date', 'asc');
}]);

If you want to load a relationship only when it has not already been loaded, you should use the loadMissing method:

public function format(Book $book)
{
    $book->loadMissing('author');

    return [
        'name' => $book->name,
        'author' => $book->author->name
    ];
}

Nested Lazy Eager Loading & morphTo

If you would prefer to eager load a morphTo relationship, and nested relationships on the various entities that may be returned by that relationship, you can use the loadMorph method.

This method will accept the name of the morphTo relationship as its first argument, and an array of model / relationship pairs as the second argument. To illustrate this method, let us consider the following model:

<?php

use Illuminate\Database\Eloquent\Model;

class ActivityFeed extends Model
{
    /**
     * Get the parent of the activity feed record.
     */
    public function parentable()
    {
        return $this->morphTo();
    }
}

In this instance, let us assume Event, Photo, and Post models can create ActivityFeed models. In addition, let us assume that Event models belong to a Calendar model, Photo models are then associated with Tag models, and the Post models belong to an Author model.

Using these model relationships and definitions, we can retrieve ActivityFeed model instances and eager load all the parentable models as well as their respective nested relationships:

$activities = ActivityFeed::with('parentable')
    ->get()
    ->loadMorph('parentable', [
        Event::class => ['calendar'],
        Photo::class => ['tags'],
        Post::class => ['author'],
    ]);

Inserting & Updating Related Models

The Save Method

Eloquent will provide convenient methods for adding new models to relationships. For instance, perhaps you want to insert a new Comment for a Post model. Rather than manually setting the post_id attribute on the Comment, you can insert the Comment directly from the relationship's save method:

$comment = new App\Comment(['message' => 'A new comment.']);

$post = App\Post::find(1);

$post->comments()->save($comment);

Notice that we did not access the comments relationship as dynamic property. Rather, we have called the comments method to obtain an instance of the relationship. The save method automatically adds the appropriate post_id value to the new Comment model.

If we need to save multiple related models, we may use the saveMany method:

$post = App\Post::find(1);

$post->comments()->saveMany([
    new App\Comment(['message' => 'A new comment.']),
    new App\Comment(['message' => 'Another comment.']),
]);

Recursively Saving Models & Relationships

If we would like to save your model as well as all of its associated relationships, we may use the push method:

$post = App\Post::find(1);

$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';

$post->push();

The Create Method

In addition to the saveMany and save methods, you can also use the create method, which will accept an array of attributes, create a model, and insert it into the database. Again, the difference between create and save is that save accepts a full Eloquent model instance whereas create accepts a plain PHP array:

$post = App\Post::find(1);

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

You can use the createMany method to create multiple related models:

$post = App\Post::find(1);

$post->comments()->createMany([
    [
        'message' => 'A new comment.',
    ],
    [
        'message' => 'Another new comment.',
    ],
]);

You can also use the findOrNew, firstOrNew, firstOrCreate and updateOrCreate methods to create or update models on relationships.

Belongs To Relationships

When you update a belongsTo relationship, you can use the associate method. This method sets the foreign key on the child model:

$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();

When we remove a belongsTo relationship, we may use the dissociate method. This method sets the relationship's foreign key to null:

$user->account()->dissociate();

$user->save();

Default Models

The belongsTo, hasOne, hasOneThrough, and morphOne relationships allows us to define a default model that will be returned if the given relationship is null. This pattern is referred to as the Null Object pattern most times and helps remove conditional checks in our code. In the following instance, the user relation returns an empty App\User model if no user is attached to the post:

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault();
}

If we want to populate the default model with attributes, we may pass a Closure or an array to the withDefault method:

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault([
        'name' => 'Guest Author',
    ]);
}

/**
 * Get the author of the post.
 */
public function user()
{
    return $this->belongsTo('App\User')->withDefault(function ($user) {
        $user->name = 'Guest Author';
    });
}

Many To Many Relationships

Attaching / Detaching

Eloquent also provides us a few additional helper methods to make working with related models more convenient. For instance, let us imagine a user can have many roles and a role can equally have many users. For us to attach a role to a user by inserting a record in the intermediate table that joins the models, we will use the attach method:

$user = App\User::find(1);

$user->roles()->attach($roleId);

When we attach a relationship to a model, we may also pass an array of additional data to be inserted into the intermediate table:

$user->roles()->attach($roleId, ['expires' => $expires]);

Sometimes it could be necessary to remove a role from a user. For us to remove a many-to-many relationship record, we will use the detach method. The detach method deletes the appropriate record out of the intermediate table; both models will however remain in the database:

// Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();

For convenience sake, the attach and detach also accept arrays of IDs as input:

$user = App\User::find(1);

$user->roles()->detach([1, 2, 3]);

$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires]
]);

Syncing Associations

We can also use the sync method to construct many-to-many associations. The sync method will accept an array of IDs to place on the intermediate table. Any of the IDs that are not in the given array will be removed from the intermediate table. So, when this operation is complete, only the IDs that are in the given array will exist in the intermediate table:

$user->roles()->sync([1, 2, 3]);

We can also pass additional intermediate table values with the IDs:

$user->roles()->sync([1 => ['expires' => true], 2, 3]);

If we do not want to detach existing IDs, we can use the syncWithoutDetaching method:

$user->roles()->syncWithoutDetaching([1, 2, 3]);

Toggling Associations

The many-to-many relationship also provides a toggle method which will "toggle" the attachment status of the given IDs. If the given ID is attached currently, it gets detached. while, if it is detached currently, it gets attached:

$user->roles()->toggle([1, 2, 3]);

Saving Additional Data On A Pivot Table

When we work with a many-to-many relationship, the save method will accept an array of additional intermediate table attributes as its second argument:

App\User::find(1)->roles()->save($role, ['expires' => $expires]);

Updating A Record On A Pivot Table

If we need to update an existing row in our pivot table, we may use updateExistingPivot method. This method will accept the pivot record foreign key and an array of attributes to update:

$user = App\User::find(1);
$user->roles()->updateExistingPivot($roleId, $attributes);

Touching Parent Timestamps

When a model belongsToMany or belongsTo another model, such as the case of a Comment which belongs to a Post, sometimes it is helpful to update the parent's timestamp when the child model is updated. For instance, when a Comment model gets updated, we may want to automatically "touch" the updated_at timestamp of the owning Post. Eloquent makes this easy. We just add a touches property containing the names of the relationships to the child model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * All of the relationships to be touched.
     *
     * @var array
     */
    protected $touches = ['post'];

    /**
     * Get the post that the comment belongs to.
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

Now, when we update a Comment, the owning Post will have the updated_at column updated as well.

Previous: Laravel (5.7) Eloquent: Getting Started
Next: Laravel (5.7) Eloquent Collections



Follow us on Facebook and Twitter for latest update.