Tìm thấy x bài viết trong xms.

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é:

  1. 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)
  2. Ngăn cách nhau bởi dấu gạch dưới (underscore product_shop)
  3. 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).
  4. 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.phpdatabase\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 =))

Đánh giá bài viết

Thích thì like
Pivot tables và mối quan hệ nhiều-nhiều (many-to-many relationship) trong Laravel
4.6666666666667/5 3 votes

Bình luận

Võ Tiến Dũng avatar
Võ Tiến Dũng

Good, cảm ơn ad.

Hiển thị bình luận Facebook