Sau khi đã học One To One, One To Many và Many To Many, chúng ta sẽ tìm hiểu thêm một kiểu Relationship đặc biệt của Eloquent là Has Many Through.
Kiểu quan hệ này cho phép truy cập các bản ghi ở bảng cuối cùng thông qua một Model trung gian mà không cần tự viết câu lệnh JOIN.
Has Many Through là gì?
Has Many Through là quan hệ một - nhiều - nhiều.
Ví dụ:
Một Country có nhiều User.
Một User có nhiều Post.
Suy ra:
Một Country có nhiều Post thông qua User.
Sơ đồ:
Country
│
└──── User
│
├── Post
├── Post
└── Post
Country không liên kết trực tiếp với Post.
Laravel sẽ đi qua bảng User.
Ví dụ cơ sở dữ liệu
Bảng countries
| id | name |
|---|---|
| 1 | Việt Nam |
| 2 | Nhật Bản |
Bảng users
| id | name | country_id |
|---|---|---|
| 1 | An | 1 |
| 2 | Bình | 1 |
| 3 | Ken | 2 |
Bảng posts
| id | title | user_id |
|---|---|---|
| 1 | Laravel | 1 |
| 2 | PHP | 2 |
| 3 | MySQL | 3 |
Quan hệ:
Country
↓
Users
↓
Posts
1. Tạo Country Model
php artisan make:model Country -m
2. Tạo bảng countries
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
3. Thêm country_id vào bảng users
Nếu bảng users đã tồn tại từ các bài trước:
php artisan make:migration add_country_id_to_users_table --table=users
Migration:
Schema::table('users', function (Blueprint $table) {
$table->foreignId('country_id')
->nullable()
->constrained()
->nullOnDelete();
});
4. Quan hệ trong các Model
Country
public function posts()
{
return $this->hasManyThrough(
Post::class,
User::class
);
}
Ý nghĩa:
- Bắt đầu từ
Country. - Đi qua
User. - Lấy tất cả
Post.
User
public function country()
{
return $this->belongsTo(Country::class);
}
📋 Chạy lệnh php artisan migrate
Tạo Country Seeder
php artisan make:seeder CountrySeeder
📋 Copy
CountrySeeder.php
use App\Models\Country;
public function run(): void
{
Country::insert([
[
'name' => 'Việt Nam',
],
[
'name' => 'Nhật Bản',
],
[
'name' => 'Hoa Kỳ',
],
[
'name' => 'Hàn Quốc',
],
]);
}
📋 Copy
Đăng ký trong DatabaseSeeder
public function run(): void
{
$this->call([
CountrySeeder::class,
]);
// các seeder khác...
}
UserFactory
'country_id' => Country::query()->inRandomOrder()->value('id'),
Khi đó chỉ cần:
User::factory()->count(10)->create();
Mỗi User sẽ được chọn Country ngẫu nhiên.
Chạy
php artisan migrate:fresh --seed
Lấy dữ liệu
/* PHẦN THÊM VÀO routes/web.php*/
use App\Models\Post;
use App\Models\Country;
Route::get('/test', function () {
$country = Country::first();
if (!$country) {
return 'Chưa có dữ liệu Country.';
}
$posts = $country->posts;
Hoặc:
foreach ($country->posts as $post) {
echo $post->title;
echo " - ";
echo $country->name;
echo "<br>";
}
Laravel sẽ tự tạo các câu lệnh JOIN cần thiết.
SQL tương đương
Laravel sẽ sinh câu lệnh có ý tưởng tương tự:
SELECT posts.*
FROM posts
JOIN users
ON posts.user_id = users.id
WHERE users.country_id = 1;
Bạn không cần tự viết SQL.
eager loading
Có thể tải trước:
$countries = Country::with('posts')->get();
if ($countries->isEmpty()) {
return 'Chưa có dữ liệu Country.';
}
Sau đó:
foreach ($countries as $country) {
echo "<h3>Quốc gia:". $country->name . "</h3>";
foreach ($country->posts as $post) {
echo $post->title . "<br>";
}
}
Điều này giúp giảm số lượng truy vấn.
Nếu muốn lấy luôn User của từng Post
Khi đó eager loading nên viết:
$countries = Country::with('posts.user')->get();
và:
foreach ($country->posts as $post) {
echo $post->title;
echo " - ";
echo "<i>Tác giả: " . $post->user->name . "</i>";
echo "<br>";
}
Đánh giá
Một ví dụ thực tế
Giả sử hệ thống bán hàng:
Customer
│
▼
Order
│
▼
OrderItem
Một Customer có nhiều Order.
Một Order có nhiều OrderItem.
Ta có thể khai báo:
public function orderItems()
{
return $this->hasManyThrough(
OrderItem::class,
Order::class
);
}
Khi đó:
$customer->orderItems;
sẽ trả về toàn bộ sản phẩm mà khách hàng đã đặt.
Khi nào nên dùng?
Has Many Through phù hợp khi:
Muốn truy cập dữ liệu gián tiếp.
Không muốn tự viết JOIN.
Muốn giữ mã nguồn ngắn gọn và dễ đọc.
Ví dụ:
Country → Posts.
Customer → OrderItems.
Company → Employees thông qua Department.
School → Students thông qua ClassRoom.
So sánh
| Relationship | Ví dụ |
|---|---|
| One To One | User → Profile |
| One To Many | User → Posts |
| Many To Many | Post ↔ Tags |
| Has Many Through | Country → Posts → User |
Tổng kết
Trong bài này chúng ta đã học:
Khái niệm Has Many Through.
Truy cập dữ liệu thông qua Model trung gian.
Khai báo
hasManyThrough().Lấy dữ liệu bằng Eloquent.
with()để tối ưu truy vấn.
Đây là một Relationship mạnh mẽ giúp giảm đáng kể số lượng câu lệnh JOIN thủ công trong các dự án lớn.
Bài tập
Tạo model
Country.Thêm khóa ngoại
country_idvào bảngusers.Khai báo
hasManyThrough()trong modelCountry.Hiển thị tất cả Post của Country có
id = 1.Sử dụng
with('posts')để lấy danh sách Country cùng các Post liên quan.
Bài tiếp theo
Ở bài sau, chúng ta sẽ tìm hiểu Polymorphic Relationship, một kiểu quan hệ linh hoạt cho phép nhiều Model khác nhau cùng chia sẻ một bảng dữ liệu, chẳng hạn như Post và Video đều có thể sử dụng chung bảng comments.
x1




Không có nhận xét nào:
Đăng nhận xét