Eloquent: relationships in laravel-Phần 1
Bài đăng này đã không được cập nhật trong 3 năm
Hôm nay mình xin giới thiệu với các bạn về cách sử dụng relationship model trong Laravel. Mỗi một PHP Framework có một cách tạo relationships model khác nhau, tuy nhiên tựu chung phía sau đó cũng chỉ là những câu lệnh SQL mà thôi. và Laravel cũng vậy.
Tài liệu tham khảo tại trang chủ của Laravel: https://laravel.com/docs/5.2/eloquent-relationships
Giới thiệu
Các bảng cơ sở dữ liệu thường liên quan tới nhau. Ví dụ, 1 bài đăng blog có thể có nhiều comments, hoặc 1 order có thể có nhiều người dùng đã đặt nó. Eloquent giúp cho việc quản lý và làm việc với các mối quan hệ này trở nên dễ dàng hơn, và hỗ trợ các loại khác nhau của relationships:
- One to One
- One to Many
- Many to Many
- Has Many Through
- Polymorphic Relations
- Many To Many Polymorphic Relations
Định nghĩa relationships
Eloquent Relationships được định nghĩa là các function trong các lớp Eloquent model. Giống như các Eloquent model, các relationships cũng hỗ trợ các query builder mạnh mẽ, định nghĩa relationship cung cấp các method chaining và query mạnh mẽ. Ví dụ:
$user->posts()->where('active', 1)->get();
Nhưng trước khi đi sâu vào cách sử dụng, chúng ta cùng tìm hiểu cách làm thế nào để xác định từng loại relationship.
One to One
Mối quan hệ one-to-one là mối quan hệ hết sức cơ bản. Ví dụ, một User
có thể được liên kết với 1 Phone
. Để xác định mối quan hệ này, chúng ta đặt một method phone
trên User model. Phương thức phone
nên trả về kết quả của 1 phương thức hasOne
trên cơ sở của 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');
}
}
Đối số đầu tiên truyền cho phương thức hasOne
là tên của model được liên kết. Một khi các mối quan hệ được xác định, chúng ta có thể truy xuất các bản ghi sư dụng Eloquent's dynamic properties. Dynamic properties cho phép bạn truy cập vào các relationship functions như thể nó là thuộc tính được định nghĩa trên các model:
$phone = User::find(1)->phone;
Eloquent giả định các foreign key dựa theo tên model.Trong trường hợp này, Phone
model sẽ tự động lấy foreign key là user_id
. Nếu bạn muốn thay đổi điều này, bạn có thể thêm vào đối số thứ 2 của hasOne
method:
return $this->hasOne('App\Phone', 'foreign_key');
Ngoài ra, Eloquent cho rằng foreign key cần phải có một giá trị phù hợp với cột id
của parent. Nói cách khác, Eloquent sẽ tìm kiếm những giá trị của cột id
của User
trong cột user_id
của các bản ghi Phone
. Nếu bạn muốn relationship sử dụng các cột khác với cột id
, bạn có thể truyền thêm 1 đối số thứ 3 trong phương thức hasOne
như sau:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
Định nghĩa các Inverse của Relationship
Vì vậy, chúng ta có thể truy cập vào Phone
model từ User
model. Bây giờ, chúng ta sẽ định nghĩa 1 relationship trên Phone
model sẽ cho phép chúng ta truy cập vào User
model. Chúng ta sử dụng phương thức nghịch đảo của hasOne
là 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, Phone
model sẽ cố gắng để match cột user_id
với 1 giá trị id
ở User
model. Tuy nhiền , nếu foreign key trên Phone
model không phải là user_id
, bạn có thể tùy chỉnh bằng cách thêm 1 đối số thứ 2 vào phương thức belongsTo
như sau:
public function user()
{
return $this->belongsTo('App\User', 'foreign_key');
}
Và cũng giống như phương thức hasOne
, bạn có thể thêm vào đối số thứ 3 để thay đổi mặc định của phương thức là đối chiếu với cột id
của User
model như sau:
public function user()
{
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
One To Many
Một quan hệ one-to-many được sử dụng để xác định các mối quan hệ khi một model sở hữu nhiều số lượng của model khác. Ví dụ, 1 blog post có nhiều comment. Giống như nhiều Eloquent relationship khác, one-to-many được xác định bằng 1 function được đặt ở model của bạn:
<?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 đúng cột foreign key trên Comment
model. Theo quy ước, Eloquent sẽ có "snake case" tên của model và hậu tố _id
. Vì vậy, trong ví dụ này, Eloquent sẽ hiểu foreign key trên Comment
model chính là post_id
.
Một khi relationship được xác định, chúng ta có thể truy cập lấy collection của comments
bằng cách truy cập lấy comments property như sau:
$comments = App\Post::find(1)->comments;
Dĩ nhiên, vì tất cả các relationship cũng phục vụ query builder, bạn cũng có thể thêm các ràng buộc để comment được lấy ra và tiếp tục thêm các chuỗi query:
$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
Giống như hasOne
, bạn cũng có thể ghi đè các foreign key là local key bằng cách thêm các đối số cho phương thức hasMany
.
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
Định nghĩa Inverse của quan hệ
Bây giờ chúng ta có thể truy cập tất cả các comments
, hãy định nghĩa một quan hệ để cho phép comments
có thể truy cập từ 1 post
. Để xác định các inverse của quan hệ hasMany
, chúng ta dùng 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');
}
}
Một khi relationship được xác nghĩa, chúng ta có thể lấy Post
model cho một Comment
bằng cách truy cập post
"dynamic property":
$comment = App\Comment::find(1);
echo $comment->post->title;
Trong ví dụ trên, Eloquent sẽ cố gắng để match một post_id
từ Comment
model với 1 id của Post
model. Eloquent xác định mặc định tên foreign key bằng cách kiểm tra tên của phương thức relationship và nối với hậu tố _id
. Tuy nhiên bạn vẫn có thể tùy chỉnh nó bằng cách thêm đối số thứ 2 trong phương thức belongsTo
như sau:
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key');
}
Nếu model cha không sử dụng id
làm khóa chính của nó, bạn có thể tùy chọn nó bằng đối số thứ 3 của phương thức belongsTo
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
Many To Many
many-to-many, mối quan hệ hơi phức tạp hơn so với hasOne
và hasMany
. Một ví dụ cho mối quan hệ này như là 1 user sẽ có nhiều roles và 1 role cũng sẽ thuộc về nhiều user. Để xác định relationship này, cần thiết phải có 3 bảng: users, roles và user_role. Bảng user_role sẽ chứa 2 column user_id và role_id.
Quan hệ many-to-many được định nghĩa bằng cách gọi phương thức belongsToMany
dựa trên Eloquent class. Ví dụ, hãy định nghĩa phương thức roles
trên 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');
}
}
Một khi các mối quan hệ được xác định, bạn có thể truy cập vào roles
bằng cách truy cập dynamic property:
$user = App\User::find(1);
Dĩ nhiên, giống như tất cả các relationship khác, bạn có thể gọi phương thức roles
và tiếp tục cho thêm vào các query:
$roles = App\User::find(1)->roles()->orderBy('name')->get();
Như đã đề cập trước đó, để xác định tên bảng của bảng tham gia vào relationship, Eloquent sẽ join 2 model liên quan theo thứ tự của bảng chữ cái. Tuy nhiên, bạn cũng có thể ghi đè quy ước này. Bạn có thể làm như vậy bằng cách thêm vào 1 đối số thứ 2 trong phương thức belongsToMany
như sau:
return $this->belongsToMany('App\Role', 'role_user');
Ngoài tùy biến trên, bạn có thể tùy biến các tên cột của các keys bằng cách truyền thêm đối số cho phương thức belongsToMany
. Đối số thứ 3 là tên foreign key mà bạn đang xác định relationship, trong khi đối số thứ 4 là tên foreign key trong model mà bạn đang join đến.
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
Định nghĩa Inverse của quan hệ
Để xác định các nghịch đảo của mối quan hệ many-to-many, bạn chỉ cần đặt một phương thức belongsToMany
trên model của bạn. Chúng ta hay định nghĩa phương thức users
trên 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');
}
}
Retrieving Intermediate Table Columns
Như bạn đã biết, làm việc với mối quan hệ many-to-many đòi hỏi cần thêm 1 bảng trung gian. Eloquent cung cấp một số cách hữu ích để tương tác với bảng này. Ví dụ, chúng ta hãy giả định đối tượng đang sử dụng của chúng ta đã có nhiều đối tượng Role
. Sau khi truy cập mối quan hệ này, chúng ta có thể truy cập vào bảng trung gian bằng cách sử dụng pivot attribute trên model.
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Chú ý rằng, mỗi Role
model chúng ta nhận được tự động gán một pivot attribute. Thuộc tính này chứa 1 model đại diện cho bảng trung gian, và có thể sử dụng như bất kỳ Eloquent model nào khác.
Theo mặc định, chỉ có các keys sẽ có mặt trên các pivot object. Nếu bảng pivot của bạn chứa các thuộc tính mở rộng, bạn phải xác định chúng khi xác định các mối quan hệ:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
Nếu bạn muốn bảng pivot của bạn tự động có created_at và updated_at timestamps, sử dụng các phương thức Timestamps vào trong định nghĩa của mối quan hệ:
return $this->belongsToMany('App\Role')->withTimestamps();
Filtering Relationships Via Intermediate Table Columns
Bạn cũng có thể lọc các kế quả trả về bởi belongsToMany
bằng cách sử dụng phương thức wherePivot
and wherePivotIn
khi định nghĩa các mối quan hệ:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('approved', [1, 2]);
Has Many Through
Quan hệ "has-many-through" cung cấp một thuận tiện short-cut để truy cập vào các mối quan hệ xa thông qua một mối quan hệ trung gian. Ví dụ, một Country
model có thể có nhiều Post
model thông qua một User
model trung gian. Trong ví dụ này, bạn có thể dễ dàng lấy tất cả các blog post cho 1 country. Hãy nhìn vào các bảng cần thiết để xác định 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
Mặc dù post
không chứa cột country_id
, mối quan hệ hasManyThrough
cung cấp quyền truy cập vào post của country thông qua $country->posts
. Để thực hiện các truy vấn này, Eloquent kiểm tra các country_id
trên bảng user trung gian. Sau khi tìm ra id
của user phù hợp, chúng được sử dụng để truy vấn bảng posts.
Bây giờ chúng ta đã xem xét các cấu trúc bảng cho các mối quan hệ, hãy định nghĩa nó trên 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');
}
}
Đối số đầu tiên truyền cho phương thức hasManyThrough
là tên của model cuối cùng chúng ta muốn truy cập, trong khi đối số thứ 2 là tên của model trung gian.
Nếu bạn muốn tùy chỉnh các foreign key của relationship, bạn có thể truyền vào các đối số thứ 3 và thứ 4 của phương thức hasManyThrough
. Đối số thứ 3 là foreign key của model trung gian, đối số thứ 4 là foreign key của model cuối cùng và đối 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'
);
}
}
All rights reserved