Eloquent: Relationships

Giới thiệu

Các bảng trong cơ sở dữ liệu thường có liên quan tới một bảng khác. Ví dụ một blog có thể có nhiều comment, hay một đơn hàng sẽ phải có thông tin liên quan của người dùng mà đã đặt nó. Eloquent giúp cho quản lý và làm việc với những quan hệ này một cách đơn giản và hỗ trợ nhiều kiểu quan hệ:

Định nghĩa relationships

Eloquent relationships được định nghĩa như một hàm trong class Eloquent model. Vì vậy, giống như là Eloquent mô tả chính mình, các quan hệ cũng phục vụ rất mạnh mẽ bởi query builders, định nghĩa các relationshipsnhư các hàm cũng cấp các ràng buộc mạnh mẽ và khả năng truy vấn. Ví dụ. chúng ta có thể nối thêm constraints vào posts relationship:

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

Nhưng trước khi đi sâu vào việc sử dụng các mối relationships, hãy học cách định nghĩa mỗi loại.

One To One

Quan hệ một - một một quan hệ đơn giản. Ví dụ, một User model có thể liên quan với một Phone. Để định nghĩa mối quan hệ này, chúng ta tạo một phương thức phone trong User model. Hàm phone sẽ trả về kết quả của phương thức hasOne dựa trên lớp Eloquent model:

<?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');
    }
}

Tham số đầu tiên truyền vào phương thức hasOne là tên của model liên quan. Một khi quan hệ đã được định nghĩa, chúng ta có thể truy xuất bản ghi liên quan bằng cách sử dụng các thuộc tính động của Eloquent. Các thuộc tính này cho phép bạn truy cập các hàm về mối quan hệ như là các thuộc tính đã được định nghĩa trong model:

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

Eloquent giả sử khóa ngoại của relationship dựa trên tên model. Trong trường hợp này model Phone model sẽ tự động được giả sử có một khóa ngoại tên user_id. Nếu bạn muốn ghi đè quy tắc này, bạn có thể truyền vào một tham số thứ 2 vào phương thức hasOne:

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

Ngoài ra, Eloquent giả sử rằng khóa ngoại có 1 giá trị tương ứng với cột id (hay khóa chính do bạn đặt $primaryKey) của bảng chứa khóa chính. Hay nói một cách khác, Eloquent sẽ tìm kiếm giá trị của user id trong cột user_id của record Phone. Nếu bạn muốn sử dụng một cột tên khác id, bạn sẽ phải truyền vào phương thức hasOne một tham số thứ 3 để chị định khóa chính này:

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

Định nghĩa đảo ngược của Relationship

Chúng ta có thể truy cập model Phone từ model User. Bây giờ hãy định nghĩa một quan hệ trên model Phone để truy cập User có sô điện thoại nào. Chúng ta có thể định nghĩa ngược hàm hasOne bằng cách sử dụng hàm belongsTo:

<?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');
    }
}

Trong ví dụ trên, Eloquent sẽ có gắng so sánh user_id từ model Phone với id trong User. Eloquent xác định tên khóa ngoại mặc định bằng tên của bảng chứa khóa chính kèm theo sau là _id. Tuy nhiên nếu khóa ngoại model Phone không phải là user_id, bạn phải truyền tên khóa ngoại này như là tham số thứ 2 của hàm belongsTo:

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

Nếu model cha không sử dụng id là primary key, hoặc bạn càn join với một cột khác, bạn phải truyền vào tham số thứ 3 vào hàmbelongsTo để chỉ định khóa chính này:

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

One To Many

Một "one-to-many" là quan hệ mà một model có nhiều model khác. Ví dụ một bài viết có vố số các bình luận. Giống như các mối quan hệ khác trong Eloquent, quan hệ này được định nghĩa bằng một hàm trong model. Ví dụ, một bài viết có thể có nhiều comments. Giống như tất cả các quan hệ Eloquent khác, quan hệ one-to-many dược định nghĩa ở trong model Eloquent:

<?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');
    }
}

Nhớ rằng, Eloquent sẽ tự động xác định cột khóa ngoại trên model Comment. Khá thuận tiện, Eloquent sẽ sử dụng dịnh dạng "snake case" là tên của model cộng với _id. Vì vậy, đối với ví dụ này, Eloquent sẽ coi khóa ngoại của model Commentpost_id.

Khi quan hệ đã được định nghĩa, chúng ta có thể truy cập danh sách comment bằng cách sử dụng thuộc tính comments. Hãy nhớ, Eloquent cung cấp các "thuộc tính động", chúng ta có thể truy cập các hàm quan hệ nếu như chúng được định nghĩa như thuộc tính của model:

$comments = App\Post::find(1)->comments;

foreach ($comments as $comment) {
    //
}

Tất nhiên, tất cả các quan hệ cũng được dùng như query builders, bạn có thể thêm các ràng buộc khác cho những comments bằng cách gọi phương thức comments và tiếp tục thêm các điều kiện vào truy vấn:

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

Giống như phương thức hasOne, bạn cũng có thể định nghĩa khóa chính và khóa ngoại riêng cho mình bằng cách truyền tham số vào phương thức hasMany:

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

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

One To Many (Inverse)

Chúng ta bây giờ đã có thể truy cập toàn bộ comment của bài post trên blog, tiếp theo hãy định nghĩa một quan hệ để cho phép comment có thể truy cập vào bài viết, chúng ta sẽ dùng phương thức hasMany, định nghĩa một hàm ngược trong model con gọi phương thức belongsTo:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

Khi quan hệ đã được định nghĩa, chúng ta có thể lấy thông tin model Post cho một Comment bằng cách truy cập vào "thuộc tính động" post:

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

echo $comment->post->title;

Trong ví dụ trên, Eloquent sẽ cố gắng đối chiếu post_id từ Comment đến một id trong model Post. Eloquent mặc định định nghĩa tên khóa ngoại bằng tên của phương thức quan hệ với đuôi _id. Tuy nhiên, nếu khóa ngoại trong model Comment không phải post_id, bạn có thể truyền tên khóa như là đối số thứ hai của hàm belongsTo:

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

Nếu model của bạn không sử dụng id làm khóa chính, hoặc bạn muốn join với model con ở một cột khác, bạn có thể truyền nó vào như là tham số thứ 3 trong phương thức belongsTo để chỉ định key của bảng cha:

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

Many To Many

Quan hệ nhiều nhiều có hơi phức tạp hơn quan hệ hasOnehasMany. Ví dụ như là mối quan hệ của 1 user với nhiều "role" (vai trò, quyền, kiểu như admin, mod,...), khi mà các role cũng được đảm nhận bởi nhiều user. Cụ thể hơn, nhiều user có thể có cùng role "Admin". Để định nghĩa mối quan hệ này, cần đến 3 bảng: users, roles, và role_user. Bảng role_user xuất phát từ tên của những bảng hay model liên quan, và bao gồm các cột user_idrole_id columns.

Quan hệ nhiều nhiều được định nghĩa bởi phương thức belongsToMany method. Ví dụ sau sẽ định nghĩa phương thức roles trong model User:

<?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');
    }
}

Khi quan hệ được định nghĩa, bạn có thể truy cập vào các role của user thông quan thuộc tính roles:

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

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

Tất nhiên cũng giống như tất cả các kiểu quan hệ khác, bạn cũng có thể gọi phương thức roles và thêm vào nó các ràng buộc:

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

Như đã nhắc đến từ trước, để xác định tên của table để join quan hệ này, Eloquent sẽ join 2 model tên liên quan theo thứ tự alphabetical. Tuy nhiên bạn có thể đặt tên tùy chọn bằng cách truyền vào tham số thứ 2 trong phương thức belongsToMany:

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

Để tùy chọn lựa chọn cột để join, bạn có thể truyền thêm tham số vào hàm belongsToMany.với tham số thứ 3 là tên của khóa ngoại ứng với model định nghĩa quan hệ và tham số thứ 4 là tên cột của khóa ngoại ứng với model tham chiếu đến model:

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

Định nghĩa quan hệ đảo

Để truy ngược lại của quan hệ many-to-many, bạn chỉ đơn giản đặt hàm belongsToMany trong model liên quan. Tiếp tục với ví dụ về user và role, hãy định nghĩa phương thức users trong model Role:

<?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');
    }
}

Bạn có thể thấy, quan hệ được định nghĩa giống hết như bên User cvới khác biệt duy nhất là chúng ta tham chiếu tới model App\User. Khi chúng ta sử dụng lại phương thức belongsToMany, tất cả các bảng và khóa tùy chọn đều có sẵn khi định nghĩa "the inverse" (có thể hiểu như truy xuất ngược) của quan hệ many-to-many.

Lấy các cột của bảng trung gian

Như chúng ta đã biết, khi làm việc với quan hệ nhiều nhiều, ta cần tới 1 bảng trung gian. Eloquent cung cấp nhiều cách rất hữu ích để tương tác với bảng này. Ví dụ hãy giả sử đối tượng User có nhiều đối tượng Rolemà nó liên quan đến. Sau khi truy cập vào quan hệ này, chúng ta có thể muốn lấy thông tin bảng trung gian bằng cách sử dụng thuộc tính pivot trong model:

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

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

Chú ý rằng mỗi model Role chúng ta lấy ra sẽ được tự động gán cho một thuộc tính pivot.Thuộc tính này bao gồm 1 model đại diện cho bảng trung gian, và có thể được sử dụng dung như bất kì model Eloquent nào.

Mặc định, chỉ có các khóa của model tồn tại trong đối tượng pivot. Nếu bảng pivot của bạn có nhiều thuộc tính hơn, bạn phải chỉ định chúng khi định nghĩa quan hệ:

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

Nếu bạn muốn bảng pivot này cũng có các automatically maintained created_atupdated_at timestamps, sử dụng hàm withTimestamps trong định nghĩa quan hệ:

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

Lọc các quan hệ qua tên cột

Bạn có thể lọc lấy kết quả trả về bởi belongsToMany sử dụng hàm wherePivotwherePivotIn khi định nghĩa quan hệ:

return $this->belongsToMany('App\Role')->wherePivot('approved', 1);

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

Has Many Through

Quan hệ "has-many-through" cung cấp cho ta một "đường tăt" tiện lợi cho việc truy cập những sự quan hệ "xa" thông qua một quan hệ trung gian. Ví dụ model Country có thể có nhiều model Post thông qua model User. Trong ví dụ này, bạn có thể dễ dàng nhóm tất cả các bài viết blog cho một country đã cho. Hãy nhìn vào các bảng cần thiết để định nghĩa mối quan hệ này:

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

Bạn có thể thấy posts không chứa cột country_id, sự quan hệ hasManyThrough cung cấp cho chúng ta tuy cập tới các country's post qua $country->posts. Để thực hiện truy vấn này, Eloquent xem xét country_id trên bảng trung gian users. Sau khi tìm thấy các user ID phù hợp, chúng sẽ được dùng để truy vấn bảng posts.

Chúng ta đã vừa xem qua cấu trúc bảng cho mối quan hệ này, bây giờ sẽ định nghĩa nó trong model Country:

<?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');
    }
}

Tham số đầu tiên truyền vào phương thức hasManyThrough là tên của model cuối nơi mà chúng ta muốn lấy data, còn tham số thứ 2 là tên của model trung gian.

Thông thường các khóa ngoại Eloquent sẽ được sử dụng để thực hiện các truy vấn quan hệ. Nếu bạn muốn tùy chỉnh tên các khóa, bạn phải truyền chúng vào như là tham số thứ 3 và thứ 4 cho phương thức hasManyThrough. Tham số thứ 3 là tên của khóa ngoại trên model trung gian, tham số thứ 4 là tên của khóa ngoại trên model cuối và tham số thứ 5 là local key:

class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            'App\Post', 'App\User',
            'country_id', 'user_id', 'id'
        );
    }
}

Quan hệ đa hình

Cấu trúc bảng

Quan hệ đa hình cho phép một model có thể thuộc về nhiều hơn một model khác với một sự liên kết đơn. Ví dụ, tưởng tượng người dùng của ứng dụng có thể "like" cả post và comment. Sử dụng mối quan hệ đa hình, bạn có thể sử dụng duy nhất một bảng comments cho cả 2 ngữ cảnh trên. Đầu tiên hãy xem qua về điều kiện cấu trúc bảng để tạo nên quan hệ này:

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

Có hai cột quan trong cần ghi nhớ đó là cột commentable_idcommentable_type trong bảng comments. Cột commentable_id sẽ lưu giữ giá trị ID của post hoặc comment, trong khi đó cột commentable_type giữ tên lớp của model sở hữu. Cột commentable_type là cách mà ORM xác định "kiểu" của model sở hữu để trả về khi truy cập vào quan hệ commentable.

Cấu trúc Model

Tiếp theo, hãy xem xét các định nghĩa cần thiết cho model để tạo nên quan hệ này:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get all of the owning commentable models.
     */
    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');
    }
}

Lấy thông tin từ quan hệ đa hình

Một khi các các bảng dữ liệu và model được định nghĩa, bạn có thể truy cập vào quan hệ thông qua các model. Ví dụ để truy cập tất cả like của một post, chúng ta đơn giản chỉ cần sử dụng thuộc tính động comments:

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

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

Bạn cũng có thể lấy chính chủ của một quan hệ đa hình từ model đa hình bằng cách truy câp tên của phương thức mà gọi tới morphTo. Trong trường hợp này, đó là hàm commentable trong model Comment. Vì vậy chúng ta sẽ truy cập phương thức đó như là một thuộc tính động:

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

$commentable = $comment->commentable;

Quan hệ commentable trong Comment sẽ trả về 1 instance hoặc là Post hoặc Video, phụ thuộc vào kiểu của model sở hữu like.

Tùy chỉnh các kiểu đa hình

Mặc định, Laravel sẽ sử dụng tên lớp đầy đủ để lưu giữ kiểu của model được liên quan. Cho một thể hiện, đã có ở ví dụ trên nơi mà một Comment có thể thuộc về một Post hoặc một Video, mặc định commentable_type sẽ hoặc là App\Post hoặc App\Video, respectively. Tuy nhiên, bạn có thể muốn tách database từ cấu trúc bên trong của ứng dụng của bạn. Trong trường hợp đó, bạn phải định nghĩa một quan hệ "morph map" để thông báo cho Eloquent sử dụng tên bảng liên quan với mỗi model thay vì sử dụng tên class đầy đủ:

use Illuminate\Database\Eloquent\Relations\Relation;

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

Bạn có thể đăng ký morphMap trong hàm boot của AppServiceProvider hoặc tạo ra một service provider nếu bạn muốn.

Quan hệ đa hình nhiều - nhiều

Cấu trúc bảng

Thêm vào mối quan hệ đa hình truyền thống, bạn cũng có thể định nghĩa quan hệ đa hình nhiều - nhiều. Ví dụ 1 blog model PostVideo có thể chia sẻ 1 liên kết đa hình tới model Tag. Sử dụng quan hệ đa hình nhiều - nhiều cho phép bạn có một danh sách các tag không trùng lặp mà được chia sẻ qua các blog post và video. Đầu tiên, hãy xem về cấu trúc bảng:

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

Cấu trúc Model

Tiếp theo, chúng ta sẽ định nghĩa các mối quan hệ trên model. Model PostVideo sẽ đều có một phương thức tags gọi tới phương thức morphToMany trên lớp Eloquent:

<?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

Tiếp theo, trên model Tag, bạn nên định nghĩa một phương thức cho mỗi model liên quan tới nó. Vì vậy trong ví dụ này, chúng ta sẽ định nghĩa 1 phương thức postsvideos:

<?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');
    }
}

Lấy dữ liệu quan hệ

Khi các bảng và model đã được định nghĩa, bạn có thể truy cập vào các quan hệ qua model. Ví dụ để lấy toàn bộ các tag cho một post, bạn đơn giản chỉ cần sử dụng thuộc tính động tags:

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

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

Bạn cũng có thể lấy chủ thể của một quan hệ đa hình từ model đa hình bằng cách truy cập tên của phương thức mà thực hiện gọi tới morphedByMany. Trong trường hợp của chúng ta đó là phương thức posts hoặc videos trong model Tag. Vì vậy bạn sẽ sử dụng những phương thức này như là các thuộc tính động:

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

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

Các quan hệ truy vấn

Khi tất cả các kiểu của quan hệ Eloquent được định nghĩa qua các hàm, bạn có thể gọi những hàm này để lấy những instance của quan hệ mà không cần thực thi các truy vấn quan hệ. Thêm vào đó, tất cả kiểu của Eloquent relationships cũng có sẵn tại query builders, cho phép bạn tiếp tục gắn thêm các ràng buộc on truy vấn trước khi được thực thi SQL đến database.

Ví dụ, hãy tưởng tượng 1 blog mà một model User có nhiều model Post:

<?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');
    }
}

Bạn có thể truy vấn quan hệ posts và thêm các ràng buộc vào như sau:

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

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

Ghi nhớ rằng bạn có thể sử dụng bất kì phương thức của query builder trên mối quan hệ, vì vậy hãy khám phá tài liệu builder để học tất cả các phương thức có sẵn.

Phương thức quan hệ Vs. Thuộc tính động

Nếu bạn không cần thêm các ràng buộc vào truy vấn Eloquent relationship, bạn có thể đơn giản chỉ cần truy cập vào quan hệ nếu như nó là 1 thuộc tính. Tiếp tục ví dụ sử dụng model UserPost, chúng ta có thể truy cập toàn bộ bài viết của 1 user như sau:

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

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

Các thuộc tính động là "lazy loading", nghĩa là chúng sẽ chỉ load dữ liệu quan hệ của chúng khi bạn thực sự gọi. Bởi vì điều này, lập trình viên thường sử dụng eager loading để pre-load các quan hệ mà họ biết sẽ được xử lý sau khi load model. Eager loading cung cấp một sự giảm bớt đáng kể trong các truy vấn SQL mà sẽ phải thực thi khi load các quan hệ của model.

Querying Relationship Existence

Khi truy cập các bản ghi một model, bạn có thể muốn giới hạn kết quả trả về dựa trên sự tồn tại của một quan hệ. Ví dụ, tưởng tượng bạn muốn lấy tất cả các post của blog mà có ít nhật một comment. Để làm việc này, bạn phải truyền tên của quan hệ vào phương thức has:

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

Bạn cũng có thể chỉ định thêm toán tử và số lượng vào truy vấn:

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

Cú pháp has lồng cũng có thể sử dụng kí hiệu "dấu chấm". Ví dụ bạn muốn lấy tất cả các post có tối thiểu 1 comment và 1 vote:

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

Nếu bạn vấn muốn có những truy vấn mạnh hơn, hãy sử dụng phương thức whereHasorWhereHas để đặt các điều kiện "where" bên trong truy vấn has. Những phương thức này cho phép bạn thêm những ràng buộc tùy chọn, như là kiểm tra nội dung của 1 comment:

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

Querying Relationship Absence

Khi truy cập vào các bản ghi của một model, bạn có thể muốn giới hạn kết quả dựa trên quan hệ. Ví dụ, tưởng tượng bạn muốn nhật tất cả bài posts không có bất kỳ comments nào. Để làm điều này, bạn truyền vào tên của quan hệ vào trong hàm doesntHave:

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

Nếu bạn cần kết quả tốt hơn, bạn có thể sử dụng hàm whereDoesntHave đặt điều kiện "where" trong doesntHave queries. Phương thức cho phép bạn tùy biến hạn chế quan hệ ràng buộc, như là kiểm tra nội dung của một comment:

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

Đếm các kết quả của quan hệ

Nếu bạn muốn đếm số kết quả từ 1 quan hệ mà không muốn load chúng bạn có thể sử dụng phương thức withCount, nó sẽ đặt một cột {relation}_count vào model kết quả. Ví dụ:

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

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

Bạn có thể lấy "counts" cho nhiều quan hệ cũng giống như thêm ràng buộc vào truy vấn:

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

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

Eager Loading

Khi bạn truy cập Eloquent relationship như là các thuộc tính, các dữ liệu này là "lazy loaded". Điều này có nghĩa là dữ liệu không thực sự load cho đến khi bạn truy cập lần đầu tiên tới thuộc tính. Tuy nhiên, Eloquent có thể "eager load" các quan hệ tại thời điểm bạn truy vấn và model cha. Eager loading làm giảm bớt vấn đề truy vấn N + 1. Để giải thích vấn đề truy vấn N + 1, ví dụ như model Book có liên quan đến Author:

<?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');
    }
}

Bây giờ, lấy toàn bộ sách và các tác giả của chúng:

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

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

Vòng lặp sẽ thực hiện 1 truy vấn để lấy tất cả các sách trong bảng, sau đó là mỗi truy vấn cho 1 cuốn sách để lấy tác giả. Vì vậy nếu chúng ta có 25 quyển sách, vòng lặp sẽ thực hiện 26 truy vấn: 1 để lấy sách, 25 để lấy tác giả của mỗi quyển sách.

Rất may, chúng ta có thể sử dụng eager loading để giảm thiểu tính toán chỉ với 2 truy vấn. Khi thực hiện truy vấn, bạn chỉ định quan hệ này sẽ được eager load bằng cách dùng phương thức with:

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

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

Với tính toán này chỉ có 2 truy vấn sẽ được thực thi:

select * from books

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

Eager Loading Multiple Relationships

Đôi khi bạn có thể cần eager load vài mối quan hệ khác nhau trong 1 tính toán. Để làm việc này, chỉ cần truyền thêm các tham số vào hàm with:

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

Nested Eager Loading

Để lồng các eager load, bạn có thể sử dụng cú pháp "dấu chấm". Ví dụ eager load tất cả tác giả sách và tất cả thông tin liên hệ của từng tác giả trong một cú pháp Eloquent:

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

Constraining Eager Loads

Đôi khi bạn có thể muốn eager load một quan hệ, nhưng cũng muốn chỉ định những ràng buộc thêm vào trong truy vấn của eager load. Sau đây là 1 ví dụ:

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

Trong ví dụ này, Eloquent sẽ chỉ eager load các post mà có cột title chứa từ first. Tất nhiên, bạn có thể gọi các phương thức query builder khác để làm truy vấn linh động hơn:

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

Lazy Eager Loading

Đôi khi bạn muốn eager load một quan hệ sau khi model cha đã được lấy ra. Ví dụ, nó có thể hữu ích nếu bạn muốn linh động quyết định sẽ load model liên quan nào:

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

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

Nếu bạn muốn thêm các ràng buộc vào truy vấn của eager load, bạn có thể truyền một mảng key quan hệ bạn muốn load. Giá trị mảng là một thể hiện Closure mà nó nhận thể hiện truy vấn:

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

Thêm & Cập nhật các model liên quan

Phương thức save

Eloquent cung cấp nhiều phương thức tiện lợi cho việc thêm model vào quan hệ. Ví dụ, bạn muốn chèn một Comment mới cho model Post. Thay vì thủ công set thuộc tính post_id trên Comment, bạn có thể chèn Comment trực tiếp từ phương thức save:

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

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

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

Chú ý rằng chúng ta không truy cập vào quan hệ comments như một thuộc tính động. Thay vào đó chúng ta gọi phương thức comments để có được một instance của quan hệ. Phương thức save sẽ tự động thêm giá trị post_id phù hợp vào modelComment.

Nếu bạn cần lưu nhiều model liên quan, bạn có thể sử dụng phương thức saveMany:

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

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

Phương thức create

Ngoài phương thức savesaveMany, bạn cũng có thể sử dụng phương thức create, cái mà cho phép một mảng của các thuộc tính, tạo ra 1 model, và chèn nó vào trong database. Một lần nữa, sự khác nhau giữa savecreate is that save chấp nhận một Eloquent model instance (một biến đã được gán giá trị) trong khi create chấp nhận một PHP array:

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

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

Trước khi sử dụng phương thức create, hãy chắc chắn bạn đã xem qua tài liệu mass assignment.

Belongs To Relationships

Khi bạn cập nhật một quan hệ belongsTo, bạn có thể sử dụng phương thức associate. Phương thức này sẽ thiết lập khóa ngoại trên model con:

$account = App\Account::find(10);

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

$user->save();

Khi xóa quan hệ belongsTo, bạn có thể sử dụng hàm dissociate. TPhương thức này sẽ thiết lập lại khóa ngoại cũng như quan hệ trên model con null:

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

$user->save();

Many To Many Relationships

Đính kèm / Gỡ bỏ

Khi làm việc với quan hệ many - many, Eloquent cung cấp một số phương thức hữu ích để làm việc với các model có liên quan vô cùng tiện lợi. Ví dụ, tưởng tượng 1 user có thể có nhiều role và 1 role có thể có nhiều user. Để đính kèm 1 role cho 1 user bằng cách chèn thêm 1 bản ghi vào bảng trung gian cái mà join với model, sử dụng phương thức attach:

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

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

Khi đính kèm 1 quan hệ vào model, bạn có thể truyền 1 mảng dữ liệu sẽ được chèn vào bảng trung gian:

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

Tất nhiên, đôi khi có thể là cần thiết khi xóa bỏ 1 role khỏi 1 user. Để xóa bỏ bản ghi quan hệ many - many, sử dụng phương thức detach. Hàm detach sẽ xóa bỏ bản ghi phù hợp khỏi bảng trung gian; tuy nhiên, cả hai model vẫn sẽ còn trong database:

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

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

Cho thuận tiện hơn, attachdetach cũng chập nhận mảng các ID như là đầu vào:

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

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

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

Syncing Associations

Bạn có thể cũng sử dụng phương thức sync để khởi tạo tập hợp many - many. Hàm sync chấp nhận 1 mảng ID để đưa vào bảng trung gian. Bất kì ID nào không ở trong mảng này sẽ bị xóa khỏi bảng trung gian. Vì vậy sau khi tính toán hoàn thành, chỉ có những id trong mảng sẽ tồn tại trong bảng trung gian:

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

Bạn cũng có thể truyền thêm các giá trị cho bảng trung gian cùng với ID:

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

Nếu bạn không muốn tách những ID hiện có, bạn có thể sử dụng hàm syncWithoutDetaching:

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

Saving Additional Data On A Pivot Table

Khi làm việc với quan hệ many-to-many, hàm save chấp nhận một mảng các điều kiện thuộc tính của bảng trung gian là tham số thứ hai:

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

Updating A Record On A Pivot Table

Nếu bạn muốn cập nhật một row trong bảng, bạn có thể sử dụng hàm updateExistingPivot. Hàm chấp nhận một pivot record foreign key và một mảng các thuộc tính cần cập nhật:

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

$user->roles()->updateExistingPivot($roleId, $attributes);

Touching Parent Timestamps

Khi một model belongsTo hoặc belongsToMany những model khác, như là Comment ẽ thuộc về một Post, đôi khi rất hữu ích khi cập nhật lại timestamp của model cha khi model con được cập nhật. Ví dụ khi một Comment model được cập nhật, bạn có thể muốn tự động "chạm" tới timestamp updated_at của model Post. Eloquent làm việc này dễ dàng. Chỉ cần thêm thuộc tính touches chứa tên của quan hệ đến model con:

<?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');
    }
}

Bây giờ, khi bạn cập nhật 1 Comment, Model Post sở hữu nó sẽ được cập nhật tại cột updated_at, nó rất thuận tiện để biết khi nào làm mất hiệu lực bộ nhớ cache của model Post:

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

$comment->text = 'Edit to this comment!';

$comment->save();
Nguồn: https://laravel.com/docs/5.3/eloquent-relationships