Pivot tables và mối quan hệ nhiều-nhiều (many-to-many relationship) trong Laravel
Hôm nay chúng ta cùng tìm hiểu về Eloquent trong Laravel với mối quan hệ nhiều - nhiều (many to many relationship). Thoạt đầu, có thể sẽ hơi lạ và khó định nghĩa cụm từ pivot tables nhưng tính năng này rất hữu dụng trong việc build many-to-many relationship với Laravel framework. Pivot tables về cơ bản là một bảng trung gian giữa 2 bảng chính (main tables). Chúng ta sẽ cùng tìm hiểu cụ thể ở ví dụ thực tế dưới đây.
1. Ví dụ thực tế về bảng Pivot
Trên thực tế có rất nhiều ví dụ về mối quan hệ nhiều-nhiều, một người dùng (User) có nhiều vai trò (Role) và một role được gắn bởi nhiều user, một người lái xe (Drive) thì có thể lái nhiều chiếc xe (Car), và một chiếc xe được lái bởi nhiều người, hay như một cửa hàng (Shop) bán nhiều sản phẩm (Product) và một sản phẩm thì được bán ở nhiều cửa hàng... và còn vô vàn những ví dụ nữa.
Mình sẽ lấy ví dụ Shop - Product làm ví dụ trong bài viết này nhé. Chúng ta sẽ phát thảo sơ qua database như thế này:
Bảng: shops
- – id
- – name
Bảng: products
- – id
- – name
Bảng: product_shop
- – product_id
- – shop_id
Bảng cuối cùng trong danh sách product_shop chính là bảng Pivot. Bảng pivot là bảng nối giữa 2 bảng, đúng như tên gọi "trục xoay" của nó.
Vậy bảng Pivot cần phải tuân thủ 1 số nguyên tắc sau đây, các bạn lưu ý khi thiết kế cơ sở dữ liệu (csdl) nhé:
- Tên của pivot table bao gồm tên của 2 bảng chính (ở dạng số ít đó là shop và product)
- Ngăn cách nhau bởi dấu gạch dưới (underscore product_shop)
- Sắp xếp theo thứ tự bảng chữ cái (alphabetical: p đứng trước s nên phải là product_shop).
- Phải chứa 2 cột là khóa ngoại của 2 bảng tham chiếu theo công thức tên bảng tham chiếu số ít + "_id" cho nên ta có: product_id và shop_id
2. Tạo migration
2.1. Tạo migration cho Shop model
php artisan make:model Shop -m
Chúng ta sẽ có 2 file app\Shop.php
và database\migrations\2018_08_22_194022_create_shops_table.php
app\Shop.php
// app\Shop.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Shop extends Model
{
public function products()
{
return $this->belongsToMany(Product::class);
}
}
database\migrations\2018_08_22_194022_create_shops_table.php
// database\migrations\2018_08_22_194022_create_shops_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateShopsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('shops', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('shops');
}
}
2.2. Tạo migration cho Product model
php artisan make:model Product -m
Chúng ta sẽ có 2 file app\Product.php
và database\migrations\2018_08_22_194151_create_products_table.php
app\Product.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
public function shops()
{
return $this->belongsToMany(Shop::class);
}
}
database\migrations\2018_08_22_194151_create_products_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('products');
}
}
2.3. Tạo migration bảng Pivot: product_shop
Chạy command:
php artisan make:migration create_product_shop_table
database\migrations\2018_08_22_195123_create_product_shop_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateProductShopTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('product_shop', function (Blueprint $table) {
$table->increments('id');
$table->integer('product_id')->unsigned()->index();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->integer('shop_id')->unsigned()->index();
$table->foreign('shop_id')->references('id')->on('shops')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('product_shop');
}
}
3. Thao tác với cơ sở dữ liệu
3.1 attach() - thêm
Giả sử Shop của bạn có id là $shopId muốn thêm một sản phẩm có id là $productId chúng ta làm như sau:
$shop = Shop::find($shopId);
$shop->products()->attach($productId);
Kết quả là trong bảng Pivot: product_shop có thêm 1 row mới chứa giá trị $shopId và $productId.
Ngược lại muốn attach Product và Shop ta làm ngược lại:
$product = Product::find($productId);
$product->shops()->attach($shopId);
Cũng giả sử như trên nhưng thay vì muốn thêm 1 sản phẩm, ta muốn thêm nhiều sản phẩm có id là $productId1, $productId2, $productId3 thì đơn giản như sau:
$shop = Shop::find($shopId);
$shop->products()->attach([
$productId1,
$productId2,
$productId3
]);
Nếu bảng Pivot ngoài product_id và shop_id mà bạn còn thêm trường a, b, c mà bạn muốn thêm thông tin cho trường đó thì đơn giản như sau:
$shop = Shop::find($shopId);
// attach một
$shop->products()->attach($productId, [
'a' => 'value',
'b' => 'value',
'c' => 'value',
]);
// attach nhiều
$shop->products()->attach([
$productId1,
$productId2,
$productId3
], [
'a' => 'value',
'b' => 'value',
'c' => 'value',
]);
3.2 detach() - xóa
Ngược lại với attach, detach cũng hỗ trợ xóa 1 hoặc nhiều records trong bảng Pivot (product_shop).
$shop = Shop::find(1);
// xóa một sản phẩm ra khỏi shop
$shop->products()->detach(1);
// xóa nhiều sản phẩm ra khỏi shop
$shop->products()->detach([
$productId1,
$productId2,
$productId3
]);
// xóa tất cả sản phẩm ra khỏi shop
$shop->products()->detach();
3.3 sync() - đồng bộ
Sync là sự kết hợp giữa attch và detach ở trên. Khi truyền vào tham số là $productId, hoặc mảng tham số [$productId1, $productId2, $productId3] thì sync sẽ kiểm tra, cái nào không có sẽ loại bỏ, cái nào đang có thì giữ nguyên và cái nào mới thì thêm vào.
Giả sử hiện tại Shop có $id là 1 hiện đang liên kết với các Product có $id là 1 và 2
id | product_id | shop_id |
1 | 1 | 1 |
2 | 2 | 1 |
Bây giờ ta muốn Shop có $id là 1 chỉ bán Product có $id là 2 và 3 ta làm như sau:
$shop = Shop::find(1);
// đồng bộ
$shop->products()->sync([2, 3]);
// Tương đương với
$shop->products()->attach([3]);
$shop->products()->detach([1]);
Khi bạn muốn sync nhưng kiểm tra có mới thì thêm vào, còn không có thì cũng đừng có xóa đi thì làm như sau:
$shop = Shop::find(1);
// đồng bộ
$shop->products()->syncWithoutDetaching([2, 3]);
// Tương đương với
$shop->products()->attach([3]);
3.4 Toggle - Bật tắt
Giống như công tắc, đang bật thì tắt mà đang tắt thì bật. Vậy thì nếu đã attach rồi thì detach và ngược lại.
Giả sử giống như ở trên: hiện tại Shop có $id là 1 hiện đang liên kết với các Product có $id là 1 và 2
// Giả sử hiện tại Shop có $id là 1 hiện đang liên kết với các Product có $id là 1 và 2
$shop->products()->toggle([1, 2, 3]);
// Tương đương với
$shop->products()->attach([3]);
$shop->products()->detach([1, 2]);
Cảm ơn bạn đã đọc hết bài viết này =))
Ủng hộ Chung Nguyễn Blog
Chung Nguyễn Blog sử dụng FlashVPS - Dịch vụ quản trị máy chủ chuyên nghiệp để quản lý VPS
#FlashVPS là dịch vụ cloud panel trên nền tảng web hỗ trợ khách hàng:
- * Quản lý máy chủ số lượng nhiều
- * Không có kinh nghiệm quản lý máy chủ
- * Thích sử dụng giao diện web đơn giản, trực quan hơn terminal
- * Quá nhàm chán với việc ghi nhớ và lặp lại việc gõ các câu lệnh
- * Muốn tự động hóa mọi thao tác
- * Muốn tiết kiệm thời gian quản trị máy chủ
- * Muốn tiết kiệm tiền bạc, nhân lực quản trị máy chủ 👉 https://flashvps.dev
Các bài viết trên website thường xuyên được đăng tải và cập nhật trên trang Facebook Chung Nguyễn Blog hãy tặng cho Chung một LIKE nhé! Mãi yêu các bạn!
813 👍
Bình luận
Thanh Nguyen
Quang Nguyen
Chung Nguyễn
Dùng thử sync đi bạn
mạnh
anh ơi.. em muôn lấy dữ liệu từ bảng pivot thì lấy thế nào ạ,
Chung Nguyễn
Lấy như thế này em nhé, có gì cứ để lại bình luận he
Võ Tiến Dũng
Good, cảm ơn ad.