Ở bài trước, chúng ta đã tìm hiểu quan hệ One To Many, trong đó một bản ghi có thể liên kết với nhiều bản ghi khác.
Tuy nhiên, có những trường hợp cả hai phía đều có thể liên kết với nhiều bản ghi của nhau. Ví dụ:
Một bài viết có nhiều thẻ (Tag).
Một thẻ có thể xuất hiện trong nhiều bài viết.
Một sinh viên học nhiều môn học.
Một môn học có nhiều sinh viên.
Đó chính là quan hệ Many To Many.
Laravel hỗ trợ kiểu quan hệ này thông qua Pivot Table (bảng trung gian).
Many To Many là gì?
Many To Many là quan hệ nhiều - nhiều.
Ví dụ:
Post
│
├── Laravel
├── PHP
└── MySQL
Tag
│
├── Web
├── Backend
├── Database
└── Tutorial
Một bài viết có thể mang nhiều Tag.
Một Tag cũng có thể thuộc nhiều bài viết.
Bước 1. Tạo Model + Migration
php artisan make:model Tag -m
Lệnh này tạo:
app/Models/Tag.php
database/migrations/xxxx_create_tags_table.php
abc
Bước 2. Migration bảng tags
Schema::create('tags', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); $table->timestamps(); });
Vì sao cần Pivot Table?
Nếu thêm trực tiếp tag_id vào bảng posts thì mỗi bài viết chỉ có một Tag.
Nếu thêm post_id vào bảng tags thì mỗi Tag cũng chỉ thuộc một bài viết.
Điều này không đáp ứng được quan hệ nhiều - nhiều.
Giải pháp là tạo một bảng thứ ba.
posts
id
title
tags
id
name
post_tag
post_id
tag_id
Bảng post_tag chỉ lưu mối liên kết giữa hai bảng.
Đây được gọi là Pivot Table.
Migration
Tạo bảng trung gian:
php artisan make:migration create_post_tag_table
Migration:
Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('tag_id')
->constrained()
->cascadeOnDelete();
});
Thông thường bảng Pivot không cần cột id.
belongsToMany()
Trong model Post:
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
Ý nghĩa:
Một Post có nhiều Tag.
Trong model Tag:
class Tag extends Model
{
protected $fillable = [
'name',
];
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
Ý nghĩa:
Một Tag thuộc nhiều Post.
Laravel sẽ tự tìm bảng post_tag.
Tạo TagSeeder
database/seeders/TagSeeder.php
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use App\Models\Tag; class TagSeeder extends Seeder { public function run(): void { Tag::create([ 'name' => 'Laravel' ]); Tag::create([ 'name' => 'PHP' ]); Tag::create([ 'name' => 'MySQL' ]); } }
Tạo PostTagSeeder
database/seeders/PostTagSeeder.php
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use App\Models\Post; class PostTagSeeder extends Seeder { public function run(): void { $post = Post::find(1); if ($post) { $post->tags()->sync([ 1, 2, 3 ]); } } }
👉 Dùng sync() thay vì attach() để chạy seed nhiều lần cũng không bị trùng dữ liệu.
DatabaseSeeder
public function run(): void { //phải chạy sau post và tag để tránh lỗi empty data
$this->call([ TagSeeder::class, PostTagSeeder::class, ]); }
Chạy
php artisan db:seed
Sau đó:
Lấy danh sách Tag
/* PHẦN THÊM VÀO routes/web.php*/
use App\Models\Post;
use App\Models\Tag;
Route::get('/test', function () {
$post = Post::first();
if (!$post) {
return 'Chưa có dữ liệu Post.';
}
foreach ($post->tags as $tag) {
echo $tag->name . "<br>";
}
});
Laravel sẽ tự JOIN bảng post_tag.
Lấy danh sách Post
Ngược lại:
$tag = Tag::first();
if (!$tag) {
return 'Chưa có dữ liệu Tag.';
}
foreach ($tag->posts as $post) {
echo $post->title . "<br>";
}
attach()
Thêm liên kết:
$post = Post::first();
if (!$post) {
return 'Chưa có dữ liệu Post.';
}
$post->tags()->attach(3);
Laravel sẽ thêm:
post_id = 1
tag_id = 3
vào bảng post_tag.
Có thể thêm nhiều Tag:
$post->tags()->attach([
1,
2,
3
]);
detach()
Xóa liên kết:
$post->tags()->detach(3);
Hoặc xóa tất cả:
$post->tags()->detach();
Chỉ dữ liệu trong bảng Pivot bị xóa.
Bảng posts và tags vẫn giữ nguyên.
sync()
Đây là phương thức được sử dụng nhiều nhất.
$post->tags()->sync([
1,
2,
5
]);
Laravel sẽ:
thêm Tag mới,
xóa Tag cũ không còn,
giữ lại Tag đã tồn tại.
Kết quả là bài viết chỉ còn đúng ba Tag trên.
eager loading
Có thể tải trước:
$posts = Post::with('tags')->get();
Sau đó:
foreach ($posts as $post) {
echo $post->title;
foreach ($post->tags as $tag) {
echo $tag->name;
}
}
Điều này giúp tránh hiện tượng N+1 Query.
Pivot Table có thêm dữ liệu
Đôi khi bảng trung gian không chỉ lưu khóa ngoại.
Ví dụ:
post_tag
post_id
tag_id
created_at
created_by
Hoặc:
course_student
student_id
course_id
score
joined_at
Laravel vẫn hỗ trợ rất tốt kiểu dữ liệu này.
Chúng ta sẽ tìm hiểu chi tiết ở các bài nâng cao.
Tổng kết
Trong bài này chúng ta đã học:
Quan hệ Many To Many.
Pivot Table.
belongsToMany().attach().detach().sync().with()để tối ưu truy vấn.
Many To Many được sử dụng rất nhiều trong các hệ thống Blog, CMS, E-commerce và các ứng dụng quản lý.
Bài tập
Tạo model
Tag.Tạo bảng
post_tag.Khai báo
belongsToMany()trong modelPost.Khai báo
belongsToMany()trong modelTag.Thêm ba Tag cho một Post bằng
attach().Thay đổi danh sách Tag của Post bằng
sync().Hiển thị tất cả Tag của từng Post bằng
with('tags').
Bài tiếp theo
Ở bài tiếp theo, chúng ta sẽ tìm hiểu Has Many Through, một kiểu Relationship giúp truy cập dữ liệu gián tiếp thông qua một Model trung gian mà không cần tự viết câu lệnh JOIN.
x0




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