Eloquent Relationships in Laravel 5.3 (Chap 1)
This post hasn't been updated for 8 years
Xin chào các bạn, để tiếp tục với loạt bài những điểm mới của Laravel 5.3, hôm nay mình sẽ giới thiệu về Eloquent: Relationships
. Nếu đã làm việc với DB thì chắc mọi người đều biết là một table trong DB thường liên kết với các bảng khác theo các mối quan hệ: 1-1 (One to One), 1-n (One to Many), n-n (Many to Many),... Và Laravel hỗ trợ bạn cả những mối quan hệ khác giúp cho việc thao tác và quản lý dữ liệu trở nên rất dễ dàng như: Has Many Through, Polymorphic Relations, Many To Many Polymorphic Relations. Có thể những thuật ngữ này khá khó hiểu nhưng đừng lo lắng, hãy cùng mình đi tìm hiểu về nó nhé (hehe)
Eloquent Relationships là gì?
Trước tiên thì các bạn cần biết rõ Eloquent là gì, link tham khảo thêm tại đây. Nói ngắn gọn là mỗi một table trong DB sẽ tương ứng với 1 class Model. Mỗi 1 model cho phép bạn truy vấn trực tiếp vào dữ liệu trong table tương ứng (Vd tên table là users thì tên Model sẽ là số ít của tên table: User). Còn Eloquent relationships được định nghĩa như là những function trên class Model đó. Nghe cũng có vẻ đơn giản, nhưng Laravel làm thế nào để hiểu được function đó là relationships và dựa vào những tham số gì để thực hiện truy vấn trong DB, giờ mình sẽ đi sâu vào từng quan hệ để định nghĩa rõ ràng hơn (huytsao).
One To Many
Vì quan hệ 1-1 khá đơn giản và ít sử dụng nên mình bỏ qua và đi sang luôn quan hệ 1-n. Ví dụ 1 nhà ga sẽ thuộc 1 khu vực, mà 1 khu vực có thể có nhiều nhà ga. Mối quan hệ giữa chúng sẽ là 1 khu vực - n nhà ga
. Ta đã có 2 table trong DB đó là stations (nhà ga) và areas (khu vực), tương ứng với đó là 2 model Station và Area. Để thực hiện lấy các bản ghi thông qua 2 table thì ta sẽ định nghĩa các function relationship như sau:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Area extends Model
{
/**
* Get the phone record associated with the area.
*/
public function stations()
{
return $this->hasMany('App\Station');
}
}
Trong Area
Model mình vừa viết 1 function tên là stations
. Function này sẽ trả về kết quả của function hasMany
dựa trên lớp Eloquen Model. Tham số đầu tiên truyền vào phương thức hasMany
là tên của model liên quan.
Ở đoạn code trên chúng ta chỉ truyền vào function hasMany 1 tham số là tên của Model và Eloquent sẽ mặc định khóa ngoại của Station
model là area_id
(sử dụng công thức tên khóa ngoại = tên của phương thức quan hệ + _id
) và khóa ngoại này sẽ có 1 giá trị tương ứng với column id
của table areas (hoặc thuộc tính $primaryKey được định nghĩa trong Area Model). Hay nói cách khác là Eloquent sẽ tìm giá trị của area id
trong column area_id
của bản ghi Station
. Nếu bạn không muốn khóa ngoại là mặc định cũng như sử dụng 1 tên column khác id
thì bạn có thể truyền 2 tham số vào function hasMany:
return $this->hasMany('App\Station`, 'foreign_key', 'local_key');
Và khi 1 quan hệ được định nghĩa như trên thì chúng ta có thể lấy được các bản ghi sử dụng Eloquent dynamic properties
:
$station = Area::find(1)->stations;
foreach ($stations as $station) {
//
}
Tất cả các quan hệ cũng được dùng như Query Builders
, bạn có thể thêm các điều kiện truy vấn khác như:
$station = App\Area::find(1)->stations()
->where('name', 'Tan Son Nhat')
->first();
Chỉ cần định nghĩa như trên là ta có thể truy cập model Area
từ model Station
. Ta cũng có thể truy cập ngược lại từ model Station
đến model Area
bằng phương thức belongsTo
như sau:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Station extends Model
{
/**
* Get the area that owns the station.
*/
public function area()
{
return $this->belongsTo('App\Area');
}
}
Eloquent sẽ hiểu function trên nếu định nghĩa đầy đủ sẽ là:
public function area()
{
return $this->belongsTo('App\Area', 'id', 'area_id');
}
Nếu bạn không muốn các khóa có tên mặc định như trên hoặc muốn join với model con ở một column khác thì ta lần lượt truyền vào các tham số :
public function area()
{
return $this->belongsTo('App\Area', 'foreign_key', 'other_key');
}
Và ta lấy được thông tin của Area
một cách dễ dàng bằng cách truy cập vào Eloquent dynamic properties
:
$area = App\Station::find(1)->area->address;
Many To Many
Quan hệ này cũng không khó hơn quan hệ one to many
nhiều lắm. Giả sử một khóa học có thể có nhiều môn học, và một môn học lại được sử dụng trong nhiều khóa học. Vậy quan hệ giữa khóa học là nhiều - nhiều. Để định nghĩa mối quan hệ này thì ngoài 2 bảng courses
, subjects
ta còn cần thêm 1 bảng trung gian là course_subject
(primary key của bảng này sẽ gồm 2 cột là course_id
và subject_id
). Mình sẽ sử dụng function belongsToMany
ở cả 2 model. Ví dụ trên Course model như sau:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Course extends Model
{
/**
* The subjects that belong to the course.
*/
public function subjects()
{
return $this->belongsToMany('App\Subject');
}
}
Ta có thể xác định tên của table để join quan hệ này (ở đây là course_subject
). Để tùy chọn cột để join, ta có thể truyền thêm tham số vào function 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ệ (ở đây là Course
) và tham số thứ 4 là tên column của khóa ngoại ứng với model tham chiếu đến (ở đây là Subject
):
return $this->belongsToMany('App\Subject', 'course_subject', 'course_id', 'subject_id');
Chú ý rằng Eloquent mặc định chỉ có các khóa ngoại tồn tại trong bảng trung gian. Nếu bạn muốn định nghĩa thêm vài column nào đó vào trong bảng thì ta sẽ định nghĩa như sau:
return $this->belongsToMany('App\Subject')->withPivot('column1', 'column2');
Thêm vào đó, bạn cũng có thể định nghĩa thêm các timestamps như created_at
và udpated_at
khi định nghĩa quan hệ:
return $this->belongsToMany('App\Subject')->withTimestamps();
Định nghiã xong rồi thì làm thế nào để tương tác với bảng trung gian? Rất đơn giản chỉ cần sử dụng thuộc tính pivot
trên model:
$course = App\Course::find(1);
foreach ($course->subjects as $subject) {
echo $subject->pivot->created_at;
}
Bạn cũng có thể lọc các kết quả trả về bởi belongsToMany
bằng cách sử dụng wherePivot
và wherePivotIn
:
return $this->belongsToMany('App\Subject')->wherePivot('active', 1);
return $this->belongsToMany('App\Subject')->wherePivotIn('active', [1, 2]);
Has Many Through
Nói ngắn gọn thì quan hệ này hỗ trợ cho việc truy cập những quan hệ từ xa thông qua một quan hệ trung gian. Ví dụ model Area
có thể có nhiều model Train
thông qua model Station
:
areas
id - integer
address - string
stations
id - integer
area_id - integer
name - string
trains
id - integer
station_id - integer
name - string
Nhìn vào bảng trên chúng ta dễ dàng nhận thấy table stations
chứa khóa ngoại area_id
trỏ đến id
của table areas, table trains
lại chứa khóa ngoại station_id
trỏ đến id
của table stations
. Để thực hiện truy vấn này, Eloquent sẽ tìm area_id
trên bảng trung gian stations
, sau khi thấy các station id phù hợp, chúng sẽ được dùng để truy vấn bảng trains
.
Vậy định nghĩa mối quan hệ đó trong model như thế nào? Hãy xem đoạn code bên dưới:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Area extends Model
{
/**
* Get all of the trains for the area.
*/
public function trains()
{
return $this->hasManyThrough('App\Station', 'App\Train');
}
}
Tham số đầu tiên của function hasManyThrough
là tên của model cuối nơi mà mình muốn lấy dữ liệu, tham số thứ 2 là tên của model trung gian.
Nếu muốn cài đè vào các khóa ngoại mà Eloquent mặc định thì bạn cần truyền vào 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. Ví dụ:
class Area extends Model
{
public function trains()
{
return $this->hasManyThrough(
'App\Train', 'App\Station',
'area_id', 'station_id', 'id'
);
}
}
Polymorphic Relations (Quan hệ đa hình)
Quan hệ đa hình cho phép một model có thể thuộc về nhiều hơn 1 model khác với một sự liên kết đơn. Giả sử người dùng có thể vote cả món ăn (food), cả bài viết (post). Sử dụng mối quan hệ đa hình bạn có thể chỉ cần sử dụng duy nhất 1 bảng vote cho cả 2 quan hệ trên.
foods
id - integer
name - string
description - text
posts
id - integer
title - string
url - string
votes
id - integer
body - text
voteable_id - integer
voteable_type - string
Để ý thấy column voteable_id
sẽ lưu giữ giá trị id
của food hoặc post còn column voteable_type
lưu giữ tên lớp của model sở hữu (dùng để xác định kiểu của model sở hữu trả về khi truy cập vào quan hệ voteable).
Ta định nghĩa quan hệ trong các model như sau:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Vote extends Model
{
/**
* Get all of the owning voteable models.
*/
public function voteable()
{
return $this->morphTo();
}
}
class Food extends Model
{
/**
* Get all of the food's votes.
*/
public function votes()
{
return $this->morphMany('App\Vote', 'voteable');
}
}
class Post extends Model
{
/**
* Get all of the post's votes.
*/
public function votes()
{
return $this->morphMany('App\Vote', 'voteable');
}
}
Để thực hiện truy vấn tất cả các votes của 1 món ăn (food), chúng ta chỉ cần sử dụng Eloquent dynamic properties
:
$food = App\Food::find(1);
foreach ($food->votes as $vote) {
//
}
Bạn cũng có thể lấy dữ liệu từ model Vote
bằng cách truy cập tên của phương thức gọi tới morphTo
là phương thức voteable
:
$vote = App\Vote::find(1);
$voteable = $vote->voteable;
Quan hệ voteable
trên model Vote
sẽ trả về 1 instance hoặc là Post
hoặc Food
, dựa trên kiểu của model sở hữu vote
.
Như đã nói ở trên, voteable_type
ở trên sẽ là App\Post
hoặc App\Food
. Nhưng nếu bạn muốn tách DB từ cấu trúc bên trong của ứng dụng thì bạn có thể định nghĩa một quan hệ morph map
(đăng kí trong hàm boot
của AppServiceProvider
để 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,
'foods' => App\Food::class,
]);
Lúc này column voteable_type
sẽ lưu 2 giá trị 1 là posts, 2 là foods.
Many To Many Polymorphic Relations (Quan hệ đa hình nhiều - nhiều)
Hãy xem cấu trúc bảng cho mối quan hệ này cho dễ hình dung nhé:
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
Ví dụ ở trên là 1 blog model Post và Video có thể chia sẻ 1 liên kết đa hình tới model Tag, sử dụng quan hệ nhiều - nhiều cho phép bạn lấy một danh sách các tag không trùng lặp mà được chia sẻ qua các post và video.
Ta sẽ định nghĩa các mối quan hệ trên model Post
và Video
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');
}
}
Sau đó trên model Tag
cũng sẽ định nghĩa 1 phương thức posts
và videos
:
<?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');
}
}
Khi đã định nghĩa xong, chúng ta sẽ tìm cách để lấy các dữ liệu quan hệ. Đầu tiên là lấy toàn bộ các tag cho một post, bạn đơn giản chỉ cần sử dụng Eloquent dynamic properties
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 trên model Tag. Vì vậy bạn sẽ sử dụng những phương thức này như là các Eloquent dynamic properties
:
$tag = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}
Vậy là xong các mối quan hệ trong Eloquent. Bài này mình gần như chỉ dịch tài liệu tiếng anh trên Docs còn bài sau mình sẽ giới thiệu kĩ về Querying Relations(Các quan hệ truy vấn) đến các bạn. Cảm ơn các bạn vì đã đọc đến đây! (lay2)
Tham khảo
Eloquent-relationships: https://laravel.com/docs/5.3/eloquent-relationships
All Rights Reserved