Giới thiệu:
Database table thường có mối liên hệ với nhau. Eloquent giúp quản lý và làm việc với những mối quan hệ này dễ dàng.
Define relationship:
Eloquent relationship được define như method trong model. Ví dụ:
$user->posts()->where('active', 1)->get();Quan hệ One To One:
Ví dụ 1 User tương ứng với 1 Phone. Để define mối quan hệ này, đặt 1 method phone trong User model. Method này gọi method hasOne và trả về chính nó. Method hasOne có sẵn trong model thông qua base class Illuminate\Database\Eloquent\Model:
<?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);
}
}Tham số đầu tiên pass vào method hasOne là tên của model liên quan. Khi đã define mối quan hệ, bạn có thể truy vấn record liên quan sử dụng dynamic properties của Eloquent. Dynamic properties cho phép bạn tiếp cận relationship method như đã define trong model:
$phone = User::find(1)->phone;Eloquent xác định khóa ngoại dựa vào tên của model cha. Trong trường hợp này model Phone tự động giả sử rằng có khóa ngoại user_id. Bạn cũng có thể pass tham số thứ 2 cho method hasOne:
return $this->hasOne(Phone::class, 'foreign_key');Ngoài ra, Eloquent giả sử rằng khóa ngoại có giá trị khớp với khóa chính của model cha. Tức là Eloquent sẽ tìm giá trị của cột User id khớp với user_id của Phone. Nếu bạn muốn sử dụng khóa chính khác id hoặc trong thuộc tính $primaryKey của model, pass tham số thứ 3 vào method hasOne:
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');Mối quan hệ ngược lại của One To One:
Để định nghĩa một mối quan hệ ngược lại với hasOne, tức là từ model Phone có thể tiếp cận model User sở hữu phone đó, ta dùng belongsTo:
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}Khi gọi method user, Eloquent sẽ thử tìm User model có id khớp với user_id của Phone. Eloquent xác định khóa ngoại thông qua _id. Nếu khóa ngoại không phải là user_id, bạn có thể pass tham số thứ 2 vào method belongsTo:
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key');
}Nếu model cha không sử dụng id làm khóa chính, bạn có thể pass tham số thứ 3 để chỉ định:
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}One To Many:
Mối quan hệ One To Many là mối quan hệ mà 1 model là cha của một hoặc nhiều model con. Ví dụ 1 post có nhiều comment:
<?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);
}
}Tương tự, Eloquent tự động xác định khóa ngoại cho Comment model (ở đây là post_id). Khi method được định nghĩa, bạn có thể tiếp cận collection các comment thông qua thuộc tính comments:
use App\Models\Post;
$comments = Post::find(1)->comments;
foreach ($comments as $comment) {
// ...
}Bạn có thể thêm ràng buộc cho query:
$comment = Post::find(1)->comments()
->where('title', 'foo')
->first();Tương tự method hasOne, bạn có thể ghi đè khóa ngoại và khóa chính bằng cách truyền tham số thứ 2, 3:
return $this->hasMany(Comment::class, 'foreign_key');
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');Ngược lại của One To Many / Belongs To:
Tương tự, để định nghĩa mối quan hệ ngược lại của hasMany, sử dụng method belongsTo:
<?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);
}
}Bạn có thể truy vấn post từ 1 comment bằng thuộc tính post:
use App\Models\Comment;
$comment = Comment::find(1);
return $comment->post->title;Eloquent tự động xác định khóa ngoại là post_id. Nếu bạn muốn tự xác định khóa ngoại, thêm tham số thứ 2 vào method belongsTo:
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key');
}Nếu model cha không sử dụng khóa chính id, truyền tham số thứ 3 để chỉ định:
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}Model mặc định:
Các method belongsTo, hasOne, hasOneThrough, morphOne cho phép bạn định nghĩa model mặc định sẽ trả về nếu mối quan hệ là null. Pattern này thường được gọi là “Null Object pattern”, giúp bạn không cần phải check điều kiện trong code. Ví dụ dưới đây mối quan hệ user sẽ trả về 1 model rỗng User nếu không có user nào khớp với Post:
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault();
}Để lưu model mặc định với các thuộc tính, bạn có thể pass 1 array hoặc closure vào method withDefault():
/**
* 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';
});
}Query mối quan hệ Belongs to:
Khi query model con của 1 mối quan hệ “belongs to”, bạn có thể dùng where để truy vấn:
use App\Models\Post;
$post = Post::where('user_id', $user->id)->get();Tuy nhiên bạn có thể dễ dàng hơn khi sử dụng method whereBelongsTo, tự động xác định mối quan hệ và khóa ngoại:
$post = Post::whereBelongsTo($user)->get();Bạn có thể cung cấp 1 collection cho method whereBelongsTo. Laravel sẽ truy vấn model thuộc về bất kỳ model cha nào trong collection:
$users = User::where('vip', true)->get();
$posts = Post::whereBelongsTo($users)->get();Mặc định Laravel sẽ xác định mối quan hệ của model đã cho dựa trên tên class, tuy nhiên bạn có thể chỉ định tên mối quan hệ bằng tay – bằng cách cung cấp tham số thứ 2 cho method:
$posts = Post::whereBelongsTo($user, 'author')->get();Has One Of Many:
Thi thoảng 1 model có thể có nhiều model liên quan, bạn muốn dễ dàng truy xuất model liên quan mới nhất hoặc cũ nhất. Ví dụ, 1 User liên quan đến nhiều Order, bạn muốn define 1 cách để xử lý những đơn hàng mới nhất. Sử dụng method hasOne kết hợp với ofMany:
public function latestOrder(): HasOne
{
return $this->hasOne(Order::class)->latestOfMany();
}Tương tự, để truy xuất order cũ nhất:
/**
* Get the user's oldest order.
*/
public function oldestOrder(): HasOne
{
return $this->hasOne(Order::class)->oldestOfMany();
}Mặc định latestOfMany và oldestOfMany sẽ truy xuất model liên quan mới nhất hoặc cũ nhất dựa trên khóa chính. Tuy nhiên thi thoảng bạn muốn sử dụng tiêu chí sắp xếp khác, ví dụ bạn muốn truy xuất đơn hàng đắt tiền nhất. Sử dụng method ofMany với tham số thứ nhất là cột được sắp xếp, tham số thứ 2 là min hoặc max:
public function largestOrder(): HasOne
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}Lưu ý: PostgreSQL hiện tại chưa hỗ trợ hàm MAX đối với các cột UUID nên không thể sử dụng được mối quan hệ one-of-many kết hợp với các cột UUID trong PostgreSQL.
Chuyển đổi quan hệ “Many” thành “Has One”:
Thông thường khi truy xuất 1 single model sử dụng method latestOfMany, oldestOfMany, ofMany, bạn đã có sẵn 1 mối quan hệ “has many” đã được định nghĩa cho model. Để thuận tiện, Laravel cho phép bạn dễ dàng chuyển đổi mối quan hệ này thành “has one” bằng cách gọi method one:
/**
* 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');
}Mối quan hệ “Has One Of Many” nâng cao:
Có thể xây dựng mối quan hệ “has one of many” nâng cao hơn. Ví dụ 1 Product có thể có nhiều liên quan đến Price được giữ trong hệ thống (kể cả khi giá mới đã được update). Ngoài ra, dữ liệu giá mới có thể được publish trước để có hiệu lực trong tương lai (thông qua cột published_at). Chúng ta cần truy xuất giá mới nhất được publish mà thời gian ở hiện tại. Nếu có 2 mức giá cùng ngày publish, ưu tiên mức giá ID cao hơn. Để thực hiện, cần pass 1 array vào method ofMany mà chứa các cột có thể sắp xếp nhằm xác định mức giá mới nhất. Ngoài ra 1 closure có thể được cung cấp như một tham số thứ 2, thêm các ràng buộc ngày publish cho 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:
Has One Through định nghĩa 1 mối quan hệ one-to-one với model khác. Tuy nhiên mối quan hệ này chỉ ra rằng model khai báo có thể khớp với 1 instance của model khác bằng cách tiến hành thông qua model thứ 3.
Ví dụ: trong 1 cửa hàng sửa chữa phương tiện, mỗi model Mechanic có thể kết hợp với 1 Car model, và mỗi Car có thể kết nối với 1 Owner model. Trong khi mechanic và owner không có mối quan hệ trực tiếp trong database, mechanic có thể tiếp cận owner thông qua Car model. Xem bảng sau về mối quan hệ giữa các model:
mechanics
id - integer
name - string
cars
id - integer
model - string
mechanic_id - integer
owners
id - integer
name - string
car_id - integerBây giờ chúng ta sẽ định nghĩa mối quan hệ cho model Mechanic:
<?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);
}
}Tham số đầu tiên của method hasOneThrough là tên model cần tiếp cận, tham số thứ 2 là tên model trung gian.
Hoặc nếu các mối quan hệ liên quan đã define sẵn trong các model, bạn chỉ cần gọi method through và cung cấp tên những mối quan hệ đó. Ví dụ Mechanic đã có mối quan hệ cars và Car đã có mối quan hệ owner, bạn có thể define “has-one-through” như sau:
// Cú pháp dựa trên string:
return $this->through('cars')->has('owner');
// Cú pháp động:
return $this->throughCars()->hasOwner();Key Conventions:
Thông thường quy ước khóa ngoại được dùng khi thực hiện query mối quan hệ. Nếu bạn muốn tùy biến key của mối quan hệ, pass key như tham số thứ 3 và 4 vào method hasOneThrough. Tham số thứ 3 là tên khóa ngoại của model trung gian. Tham số thứ 4 là tên khóa ngoại model cuối. Tham số thứ 5 là khóa chính, tham số thứ 6 là khóa chính của model trung gian:
class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // Foreign key on the cars table...
'car_id', // Foreign key on the owners table...
'id', // Local key on the mechanics table...
'id' // Local key on the cars table...
);
}
}Quan hệ Many To Many:
Ví dụ 1 user có nhiều roles và những roles này lại được share bởi những users khác. Để define mối quan hệ này, cần 3 bảng là users, roles và role_user. Bảng role_user có 2 cột là user_id và role_id. Mối quan hệ này được define bằng cách viết 1 method trả về kết quả của belongsToMany. Ví dụ, define 1 method role trong model User. Tham số đầu tiên là tên của model liên quan:
class User extends Model
{
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
}Bạn có thể tiếp cận user roles sử dụng thuộc tính roles:
$user = User::find(1);
foreach ($user->roles as $roles) { ... }Do các mối quan hệ cũng là query builder, bạn có thể thêm ràng buộc cho query bằng cách nối chuỗi:
$roles = User::find(1)->roles()->orderBy('name')->get();Để xác định tên bảng trung gian, Eloquent sẽ ghép tên 2 model theo thứ tự alphabet. Tuy nhiên bạn cũng có thể ghi đè quy ước này bằng cách pass tham số thứ 2 vào method belongsToMany:
return $this->belongsToMany(Role::class, 'role_user');Bạn cũng có thể customize tên cột của các khóa của các bảng bằng cách pass tham số vào method belongsToMany. Tham số thứ 3 là khóa ngoại của model chính (model mà bạn đang define quan hệ), tham số thứ 4 là khóa ngoại của model mà bạn kết nối đến:
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id')Define inverse của quan hệ:
Để define inverse của quan hệ “many-to-many”, bạn cần define 1 method ở model kia mà trả về kết quả của belongsToMany. Ở ví dụ trên ta định nghĩa method users cho Role model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}Truy vấn bảng trung gian:
Quan hệ many-to-many cần 1 table trung gian. Ví dụ, model User có nhiều Role, để tiếp cận table trung gian role_user, sử dụng thuộc tính pivot trong method:
user App\Models\User;
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}Mỗi Role model được tự động gán 1 thuộc tính pivot. Thuộc tính này chứa 1 model đại diện cho table trung gian.
Mặc định chỉ có khóa của model mới xuất hiện trên pivot. Nếu table trung gian có thêm thuộc tính khác, bạn phải chỉ định nó khi define quan hệ:
return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');Nếu bạn muốn có created_at và updated_at tự động, gọi method withTimestamps khi define quan hệ:
return $this->belongsToMany(Role::class)->withTimestamps();Đổi tên thuộc tính pivot:
Đôi khi bạn cần đổi pivot thành 1 tên khác để phản ánh ý nghĩa của nó. Ví dụ users đăng ký các podcasts, trong trường hợp này bạn muốn thay đổi pivot thành subscription:
return $this->belongsToMany(Podcast::class)->as('subscription');Khi đó bạn có thể tiếp cận table trung gian qua subscription:
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}Filter query thông qua table trung gian:
Bạn có thể filter kết quả trả về của belongsToMany sử dụng wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull, wherePivotNotNull:
return $this->belongsToMany(Role::class)
->wherePivot('approved', 1);
return $this->belongsToMany(Role::class)
->wherePivotIn('priority', [1, 2]);
return $this->belongsToMany(Role::class)
->wherePivotNotIn('priority', [1, 2]);
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNull('expired_at');
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotNull('expired_at');Sắp xếp query thông qua table trung gian:
Bạn có thể sắp xếp kết quả trả về của belongsToMany sử dụng method orderByPivot. Ví dụ sau truy vấn tất cả huy hiệu (badges) mới nhất của user:
return $this->belongsToMany(Badge::class)
->where('rank', 'gold')
->orderByPivot('created_at', 'desc');Define model trung gian:
Nếu bạn muốn define 1 custom model để đại diện cho table trung gian, sử dụng method using. Custome pivot model giúp bạn thực hiện thêm 1 số method và cast.
Custom pivot sẽ extend Illuminate\Database\Eloquent\Relations\Pivot trong khi custom polymorphic (đa hình) pivot model sẽ extend Illuminate\Database\Eloquent\Relations\MorphPivot. Ví dụ bạn có thể define Role model cái mà sử dụng custom pivot RoleUser:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}Bạn cần tạo model RoleUser extend Pivot class:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
// ...
}Lưu ý: pivot model không sử dụng SoftDeletes. Nếu bạn muốn sử dụng soft delete, convert pivot sang Eloquent.
Custom pivot và ID auto-increment:
Nếu bạn đã define 1 quan hệ many-to-many sử dụng custom pivot, và pivot có khóa chính increment, bạn cần define thuộc tính incrementing = true cho pivot:
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = true;Quan hệ đa hình (Polymorphic):
Quan hệ đa hình cho phép model con thuộc về nhiều loại model khác nhau. Ví dụ Comment thuộc về cả Post và Video.
One to one (đa hình):
Cấu trúc table:
One-one đa hình tương tự one-one, tuy nhiên model con có thể thuộc về nhiều loại model khác nhau. Ví dụ Post và User có thể share quan hệ với Image. Sử dụng one-one đa hình cho phép bạn sử dụng duy nhất 1 table images để liên kết với users và posts:
posts
id - integer
name - integer
users
id - integer
name - string
images
id - integer
url - string
imageable_id - integer
imageable_type - stringCột imageable_id là giá trị ID của post hoặc user, imageable_type là tên model (user hoặc post).
Cấu trúc model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Image extends Model
{
/**
* Get the parent imageable model (user or post).
*/
public function imageable(): MorphTo
{
return $this->morphTo();
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Post extends Model
{
/**
* Get the post's image.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class User extends Model
{
/**
* Get the user's image.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}Truy vấn quan hệ: truy vấn thông qua thuộc tính động image:
use App\Models\Post;
$post = Post::find(1);
$image = $post->image;Bạn có thể truy vấn model cha bằng method imageable của model Image:
use App\Models\Image;
$image = Image::find(1);
$imageable = $image->imageable;imageable trả về 1 Post hoặc User instance tùy theo loại model nào sở hữu image.
Quy ước key:
Bạn có thể chỉ định tên cột “id” và “type”. Nếu làm vậy, lưu ý luôn pass tên quan hệ làm tham số đầu tiên của method morphTo. Thông thường chính là tên method, nên bạn có thể sử dụng hằng số PHP __FUNCTION__:
/**
* Get the model that the image belongs to.
*/
public function imageable(): MorphTo
{
return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}Các phần đa hình one-to-many, many-to-many đọc ở docs:
https://laravel.com/docs/10.x/eloquent-relationships#polymorphic-relationships
Truy vấn quan hệ:
Khi các quan hệ đã được define thông qua method, bạn có thể gọi những method đó để có được instance mà không cần query load các model liên quan. Hơn nữa các Eloquent relationship đồng thời là query builder, cho phép bạn nối các ràng buộc vào query.
Ví dụ 1 User có nhiều Post:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Model
{
/**
* Get all of the posts for the user.
*/
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}Bạn có thể truy vấn như sau:
$user = User::find(1);
$user->posts()->where('active', 1)->get();Bạn có thể sử dụng bất kỳ query builder nào lên Eloquent relationship.
Nối orWhere vào sau quan hệ:
Lưu ý khi nối mệnh đề orWhere vào quan hệ, vì mệnh đề orWhere sẽ được nhóm logic ở cùng level với quan hệ:
$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();Query trên dưới dạng SQL:
select *
from posts
where user_id = ? and active = 1 or votes >= 100Bạn có thể thấy mệnh đề or truy vấn bất kỳ post nào có hơn 100 vote. Query không còn ràng buộc với user đã được chỉ định. Cho nên trong hầu hết trường hợp, bạn nên sử dụng nhóm logic:
use Illuminate\Database\Eloquent\Builder;
$user->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();Ví dụ trên sẽ thực thi SQL chính xác như bạn mong đợi:
select *
from posts
where user_id = ? and (active = 1 or votes >= 100)Method của quan hệ và thuộc tính động:
Nếu bạn không muốn thêm các ràng buộc bổ sung vào truy vấn quan hệ, bạn có thể tiếp cận quan hệ như 1 thuộc tính. Ví dụ:
use App\Models\User;
$user = User::find(1);
foreach ($user->posts as $post) {
// ...
}Thuộc tính động thực thi “lazy loading”, nghĩa là nó chỉ load dữ liệu quan hệ khi bạn thật sự cần. Vì vậy dev thường sử dụng “eager loading” để pre-load những quan hệ sẽ tiếp cận sau khi load model. Eager loading giúp giảm đáng kể truy vấn SQL phải thực thi để load các quan hệ.
Truy vấn sự tồn tại của quan hệ:
Khi truy vấn bản ghi, bạn muốn limit kết quả dựa trên sự tồn tại của quan hệ. Ví dụ: bạn muốn truy vấn tất cả post có ít nhất 1 comment. Sử dụng method has hoặc orHas:
$posts = Post::has('comment')->get();Bạn có thể chỉ định toán tử và giá trị cho method:
$posts = Post::has('comments', '>=', 3)->get();Các câu lệnh has lồng nhau có thể xây dựng bằng dấu . ví dụ bạn cần truy vấn tất cả post có ít nhất 1 comment mà có ít nhất 1 image:
$posts = Post::has('comments.images')->get();Sử dụng whereHas và orWhereHas:
use Illuminate\Database\Eloquent\Builder;
// Retrieve posts with at least one comment containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
// Retrieve posts with at least ten comments containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();Truy vấn tồn tại quan hệ inline:
…
Hàm tập hợp cho Related Models:
Đếm số model liên quan:
Sử dụng method withCount, method này sẽ đặt thuộc tính {relation}_count vào model kết quả:
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}Query dưới sẽ truy vấn những post có comment content chứa từ code%:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;Bạn có thể đặt tên cho count result:
use Illuminate\Database\Eloquent\Builder;
$posts = 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;Deferred count loading: sử dụng method loadCount để load relationship count sau khi model đã được truy xuất:
$book = Book::first();
$book->loadCount('genres');Bạn có thể thêm ràng buộc vào count query, bạn có thể pass 1 array khóa bởi quan hệ bạn muốn count. Giá trị array là closure mà nhận về 1 query builder:
$book->loadCount(['reviews' => function (Builder $query) {
$query->where('rating', 5);
}])Relationship counting & custom select:
Bạn có thể kết hợp withCount với lệnh select:
$posts = Post::select(['title', 'body'])
->withCount('comments')
->get();Một số hàm tập hợp khác:
Một số hàm khác: withMin, withMax, withAvg, withSum, withExists. Những method này đặt thuộc tính {relation}_{function}_{column} vào kết quả trả về:
use App\Models\Post;
$posts = Post::withSum('comments', 'votes')->get();
foreach ($posts as $post) {
echo $post->comments_sum_votes;
}Nếu bạn muốn tiếp cận kết quả của hàm tập hợp bằng tên khác, bạn có thể chỉ định tên thay thế:
$posts = Post::withSum('comments as total_comments', 'votes')->get();
foreach ($posts as $post) {
echo $post->total_comments;
}Tương tự loadCount, các method khác cũng có deferred version:
$post = Post::first();
$post->loadSum('comments', 'votes');Nếu bạn kết hợp những hàm tập hợp này với lệnh select, lưu ý luôn gọi hàm sau lệnh select:
$posts = Post::select(['title', 'body'])
->withExists('comments')
->get();Đếm số model liên quan ở quan hệ đa hình:
…
Eager loading:
Khi tiếp cận quan hệ trong Eloquent, các model liên quan được lazy load, nghĩa là dữ liệu quan hệ chỉ thực sự load cho đến khi bạn tiếp cận nó lần đầu. Tuy nhiên Eloquent có thể “eager load” tại thời điểm bạn query model cha. Eager load làm giảm vấn đề “N+1” query. Để hiểu vấn đề N+1 query, giả sử 1 Book model thuộc về 1 Author model:
class Book extends Model
{
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
}Bây giờ truy vấn tất cả sách và tác giả của nó:
$books = Book::all();
foreach ($books as $book) {
echo $book->author->name;
}Loop này sẽ thực thi 1 query để truy xuất tất cả books, sau đó mỗi book lại thực hiện 1 query khác để truy xuất author. Đây là vấn đề N+1.
Sử dụng Eager load để giảm chỉ còn 2 query. Lúc build query, bạn cần chỉ định quan hệ nào sẽ được eager load sử dụng method with:
$books = Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}Câu lệnh SQL sẽ được thực hiện:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)Eager load cho nhiều quan hệ:
$books = Book::with(['author', 'publisher'])->get();Lồng eager load: sử dụng .
$books = Book::with('author.contacts')->get();Hoặc cách khác, sử dụng array lồng nhau:
$books = Book::with([
'author' => [
'contacts',
'publisher',
],
])->get();Lồng eager load cho quan hệ đa hình:
…
Eager load cho mặc định: sử dụng thuộc tính $with cho model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
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(): BelongsTo
{
return $this->belongsTo(Author::class);
}
/**
* Get the genre of the book.
*/
public function genre(): BelongsTo
{
return $this->belongsTo(Genre::class);
}
}…
Ràng buộc cho eager load: pass 1 array tới with method, array key là tên quan hệ, array value là closure add ràng buộc:
$users = User::with(['posts' => function (Builder $query) {
$query->where('title', 'like', '%code%');
}])->get();$users = User::with(['posts' => function (Builder $query) {
$query->orderBy('created_at', 'desc');
}])->get();Lazy eager loading:
Thi thoảng bạn cần eager load sau khi model cha đã được truy xuất. Ví dụ, nó có thể hữu ích nếu bạn cần quyết định động khi nào load model liên quan:
use App\Models\Book;
$books = Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}Nếu bạn cần set các ràng buộc bổ sung cho eager load, bạn có thể pass 1 array keyed bởi quan hệ mà bạn muốn load. Giá trị array là closure nhận 1 query instance:
$author->load(['books' => function (Builder $query) {
$query->orderBy('published_date', 'asc');
}]);Để load 1 quan hệ chỉ khi nó chưa được load, sử dụng loadMissing:
$book->loadMissing('author');Chặn lazy load:
Bạn có thể hướng dẫn Laravel chặn lazy load cho quan hệ bằng cách gọi preventLazyLoading. Thông thường bạn gọi method này trong boot của service provider. Method này chấp nhận 1 tham số boolean tùy chọn xác định có chặn lazy load không. Ví dụ bạn chỉ muốn chặn lazy load ở môi trường non-production nhờ vậy ở môi trường product có thể thực hiện bình thường thậm chí cả khi mã lazy load vô tình có mặt trong code:
use Illuminate\Database\Eloquent\Model;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}Sau khi chặn lazy load, Eloquent sẽ ném 1 ngoại lệ Illuminate\Database\LazyLoadingViolationException khi ứng dụng cố gắng lazy load bất kỳ quan hệ nào.
Thêm mới / Update model liên quan:
Method save:
Eloquent cung cấp các method thuận tiện để thêm mới model vào quan hệ. Ví dụ bạn cần add 1 comment mới vào post:
$comment = new Comment(['message' => 'New message']);
$post = Post::find(1);
$post->comments()->save($comment);Lưu ý rằng chúng ta không tiếp cận quan hệ comments như 1 thuộc tính động. Thay vào đó gọi method comments để có được 1 instance. Bạn có thể lưu nhiều model bằng saveMany:
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);Method save và saveMany sẽ duy trì trong các model instance đã cho, nhưng sẽ không thêm mới model được duy trì nào vào bộ nhớ đã được load lên model cha. Nếu bạn muốn tiếp cận những quan hệ đó sau khi sử dụng method save hoặc saveMany, sử dụng method refresh để reload mode và quan hệ của nó:
$post->comments()->save($comment);
$post->refresh();
// All comments, including the newly saved comment...
$post->comments;Đệ quy lưu model và quan hệ:
Nếu bạn muốn save model và tất cả quan hệ tương ứng, sử dụng method push. Ví dụ, Post model sẽ được lưu với comment và tác giả của comment:
$post = Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
$post->push();Sử dụng method pushQuietly() để lưu mà không phát sinh sự kiện nào.
Method create:
Sử dụng create method, tham số là 1 array thuộc tính, tạo mới model, insert vào database. Khác nhau giữa save và create là save chấp nhận 1 full Eloquent model còn create chấp nhận 1 array. Method trả về model mới được tạo:
use App\Models\Post;
$post = Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);Bạn có thể sử dụng createMany để tạo nhiều model liên quan:
$post = Post::find(1);
$post->comments()->createMany([
['message' => 'A new comment.'],
['message' => 'Another new comment.'],
]);Tương tự, method createQuietly và createManyQuietly sẽ không kích hoạt bất kỳ sự kiện nào.
Bạn cũng có thể sử dụng method findOrNew, firstOrNew, firstOrCreate, updateOrCreate để tạo hoặc update model trong quan hệ.
Quan hệ Belongs To:
Nếu bạn muốn gán 1 model con vào 1 model cha mới, bạn có thể sử dụng method associate. Ví dụ, model User define 1 quan hệ belongsTo đến model Account. Method associate sẽ set khóa ngoại lên model con:
use App\Models\Account;
$account = Account::find(10);
$user->account()->associate($account);
$user->save();Để gỡ 1 model cha khỏi model con, sử dụng dissociate method. Method này sẽ set khóa ngoại thành null:
$user->account()->dissociate();
$user->save();Quan hệ many to many:
Attach / Detach:
Ví dụ, user và role là quan hệ many many. Sử dụng attach để gắn 1 role vào user bằng cách chèn 1 record vào bảng trung gian:
use App\Models\User;
$user = User::find(1);
$user->roles()->attach($roleId);Khi gắn 1 quan hệ vào model, có thể pass 1 array dữ liệu cho bảng trung gian:
$user->roles()->attach($roleId, ['expires' => $expires]);Để gỡ 1 role khỏi user, sử dụng method detach:
// Detach a single role from the user...
$user->roles()->detach($roleId);
// Detach all roles from the user...
$user->roles()->detach();Method attach và detach có thể chấp nhận 1 array ID:
$user = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
1 => ['expires' => $expires],
2 => ['expires' => $expires],
]);Đồng bộ kết hợp:
Sử dụng method sync để xây dựng quan hệ many many. Method này chấp nhận 1 array ID để lưu vào table trung gian. Bất kỳ ID nào không có trong array sẽ được gỡ khỏi table trung gian. Vì vậy sau khi method hoàn thành, chỉ những ID trong array mới tồn tại trên bảng trung gian:
$user->roles()->sync([1, 2, 3]);
// pass thêm giá trị vào bảng trung gian:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);Nếu bạn muốn chèn thêm cùng 1 giá trị, sử dụng method syncWithPivotValues:
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);Nếu bạn không muốn gỡ những ID (mà không có trong array) đã tồn tại khỏi bảng trung gian, sử dụng method syncWithoutDetaching:
$user->roles()->syncWithoutDetaching([1, 2, 3]);Chuyển đổi liên kết:
Quan hệ many-many có method toggle giúp chuyển đổi trạng thái gắn của ID model quan hệ. Nếu những ID đã cho đang có, nó sẽ được gỡ. Ngược lại nếu đã gỡ, thì sẽ được gắn lại:
$user->roles()->toggle([1, 2, 3]);Update 1 record của bảng trung gian:
Sử dụng method updateExistingPivot. Method này chấp nhận 1 khóa ngoại của bảng trung gian và 1 array thuộc tính để update:
$user = User::find(1);
$user->roles()->updateExistingPivot($roleId, [
'active' => false,
]);Update timestamps của parent model:
Khi 1 model được define belongsTo hoặc belongsToMany với model khác, ví dụ Comment belongs to Post, thi thoảng bạn cần update timestamp của model cha khi model con được update. Thêm thuộc tính touches vào model con:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
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(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}Lưu ý: model cha chỉ update timestamp khi model con được update bởi method save.
