Sử dụng Polymorphic Relationship Laravel – Phần I

Đăng bởi:

Phan Sỹ Tân

Đăng ngày:

Jan 30, 2021

Đăng ở:

Tin Tức Công Nghệ

Hôm nay mình sẽ giới thiệu Polymorphic Relationship. Ở phần I này, mình sẽ làm một ví dụ để tiếp cận Polymorphic Relatiionship.

1. Giới thiệu

Như các bạn đã biết, Laravel framework hỗ trợ tính năng Eloquent Relationship cho các model nhằm dễ dàng thao tác với cơ sở dữ liệu. Mỗi loại relationship thể hiện mối quan hệ tương ứng với các bảng:

  • One to One
  • One to Many
  • Many to Many

Với phiên bản 5.8 trở lên, Laravel đã hỗ trợ Polymorphic Relationships (quan hệ đa hình). Để tiếp cận, hãy xem ví dụ dưới đây.

2. Ví dụ

Một cửa hàng bán những mặt hàng: Điện thoại, phụ kiện và linh kiện. Viết chức năng báo cáo bán hàng thông qua thông tin hoá đơn.

Để đơn giản hoá, thiết kế 4 bảng như sau:

csdl
  • Sau khi khởi tạo project, tạo migration:
public function up(){
	Schema::create('phones', function (Blueprint $table) {
		$table->id();
		$table->string('name', 200)->nullable();
		$table->string('note', 500)->nullable();
		$table->timestamps();
	});
	Schema::create('fits', function (Blueprint $table) {
		$table->id();
		$table->string('name', 200)->nullable();
		$table->string('note', 500)->nullable();
		$table->timestamps();
	});
	Schema::create('accessories', function (Blueprint $table) {
		$table->id();
		$table->string('name', 200)->nullable();
		$table->string('note', 500)->nullable();
		$table->timestamps();
	});
	Schema::create('invoice_details', function (Blueprint $table) {
		$table->id();
		$table->integer('invoice_id')->unsigned()->nullable();
		$table->string('product_type', 50)->nullable();
		$table->integer('product_id')->unsigned()->nullable();
		$table->integer('price')->default(0);
		$table->integer('quantity')->default(0);
		$table->timestamps();
	});
}
  • Tạo seeder:
public function run(){
	DB::table('phones')->truncate();
	DB::table('fits')->truncate();
	DB::table('accessories')->truncate();
	DB::table('invoice_details')->truncate();

	DB::table('phones')->insert([
		['id' => 1, 'name' => 'iPhone 11'],
		['id' => 2, 'name' => 'Samsung Galaxy 8'],
		['id' => 3, 'name' => 'Xiaomi MI 10'],
	]);

	DB::table('fits')->insert([
		['id' => 1, 'name' => 'Tai nghe Airport 2'],
		['id' => 2, 'name' => 'Bao da iPhone 11 KST'],
	]);

	DB::table('accessories')->insert([
		['id' => 1, 'name' => 'Anten wifi iPad 2'],
		['id' => 2, 'name' => 'Camera sau iPhone 11'],
		['id' => 3, 'name' => '	Xương Samsung Galaxy A7 2018 Xanh'],
	]);

	DB::table('invoice_details')->insert([
		['invoice_id' => 2, 'product_type' => 'phone', 'product_id' => 1, 'quantity' => 1, 'price' => 10000000],
		['invoice_id' => 4, 'product_type' => 'fit', 'product_id' => 2, 'quantity' => 2, 'price' => 200000],
		['invoice_id' => 5, 'product_type' => 'accessory', 'product_id' => 2, 'quantity' => 2, 'price' => 50000],
		['invoice_id' => 7, 'product_type' => 'accessory', 'product_id' => 3, 'quantity' => 1, 'price' => 50000],
	]);
}
  • Khai báo model Phone tại app\Models\Phone.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model
{
    protected $guarded = ['id'];
}
  • Khai báo model Fit tại App\Models\Fit.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Fit extends Model
{
    protected $guarded = ['id'];
}
  • Khai báo model Accessory tại app\Models\Accessory.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Accessory extends Model
{
    protected $guarded = ['id'];
}
  • Khai báo model InvoiceDetail tại app\Models\InvoiceDetail.php. Ở đây tạm thời khai báo One to Many Relationship với các thực thể Phone, Fit và Accessory
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class InvoiceDetail extends Model
{
    protected $guarded = ['id'];

    public function fit(){
        return $this->belongsTo(Fit::class, 'product_id');
    }

    public function phone(){
        return $this->belongsTo(Phone::class, 'product_id');
    }

    public function accessory(){
        return $this->belongsTo(Accessory::class, 'product_id');
    }
}
  • Tại routes\web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ReportController;

Route::get('reports', [ReportController::class, 'index']);
  • Tạị app\Http\Controllers\ReportController:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\InvoiceDetail;

class ReportController extends Controller
{
    public function index(){
        $report = InvoiceDetail::with('phone', 'fit', 'accessory')->get();
        return view('report.index', compact('report'));
    }
}
  • Tại resources/views/report/index.blade.php
@extends('layouts.master')

@section('title', 'Báo cáo bán hàng')

@section('content')

    <div class="table-responsive">
        <table class="table table-bordered table-striped table-hover">
            <tbody>
                <tr>
                    <th class="text-center">#</th>
                    <th class="text-center">Hoá đơn</th>
                    <th class="text-center">ID sản phẩm</th>
                    <th class="text-center">Sản phẩm</th>
                    <th class="text-center">Đơn giá</th>
                    <th class="text-center">Số lượng </th>
                    <th class="text-center">Thành tiền</th>
                </tr>
                @php $index = 1; @endphp
                @foreach ($report as $item)
                    <tr>
                        <td class="text-center">{{ $index++ }}</td>
                        <td class="text-center"><a href="#">#HĐ{{ $item->invoice_id }}</a></td>
                        <td class="text-center"><strong>{{$item->product_id}}</strong></td>
                        <td>
                            @switch($item->product_type)
                                @case('phone')
                                    <span class="label label-info">Điện thoại</span>
                                    {{ $item->phone->name }}
                                    @break
                                @case('fit')
                                    <span class="label label-success">Phụ kiện</span>
                                    {{ $item->fit->name }}
                                    @break
                                @case('accessory')
                                    <span class="label label-warning">Linh kiện</span>
                                    {{ $item->accessory->name }}
                                    @break
                                @default

                            @endswitch
                        </td>
                        <td class="text-right">{{ format_price($item->price) }}</td>
                        <td class="text-center">{{ $item->quantity }}</td>
                        <td class="text-right">{{ format_price($item->price *  $item->quantity) }}</td>
                    </tr>
                @endforeach
            </tbody>
        </table>

    </div>

@endsection
  • Demo
demo-poly-3-1

3. Polymorphic Relationship

Với ví dụ ở mục 2, đã sử dụng mối quan hệ One To Many Relationship. Tại ReportController:

$report = InvoiceDetail::with('phone', 'fit', 'accessory')->get();

Các truy vấn  SQL tương ứng như sau:

demo-poly-1

Các giá trị trong mệnh đề where… in là các “product_id” được tìm thấy trong câu lệnh:

select * from `invoice_details`

Các bạn chú ý vào điện thoại. kết quả hiển thị ra một điện thoại với ID là 1. Nhưng câu truy vấn lại là:

select * from `phones` where `phones`.`id` in (1, 2, 3)

Cho nên bản ghi của bảng phones có id 2 và 3 được truy vấn ra nhưng không được sử dụng. Nếu số lượng bản ghi trong bảng “invoice_details” càng lớn thì truy vấn càng lớn, lãng phí thời gian và bộ nhớ càng lớn.

Do đó ta nên sử dụng Polymorphic Relationship thay vì One To Many Relationship như hiện tại. Các bước thay đổi:

  • Tại app\Models\InvoiceDetail.php:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class InvoiceDetail extends Model
{
    protected $guarded = ['id'];

    public function product()
    {
        return $this->morphTo();
    }
}
  • Khai báo ánh xạ model ứng với mỗi loại product_type của bảng invoice_details tại app\Providers\AppServiceProvider.php, hàm boot():
public function boot()
{
	\Illuminate\Database\Eloquent\Relations\Relation::morphMap([
		'phone' => \App\Models\Phone::class,
		'fit' => \App\Models\Fit::class,
		'accessory' => \App\Models\Accessory::class,
	]);
}
  • Tại app\Http\Controllers\ReportController.php:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\InvoiceDetail;

class ReportController extends Controller
{
    public function index(){
        $report = InvoiceDetail::with('product')->get();
        return view('report.index', compact('report'));
    }
}
  • Tại resources\views\report\index.blade.php:
@extends('layouts.master')

@section('title', 'Báo cáo bán hàng')

@section('content')

    <div class="table-responsive">
        <table class="table table-bordered table-striped table-hover">
            <tbody>
                <tr>
                    <th class="text-center">#</th>
                    <th class="text-center">Hoá đơn</th>
                    <th class="text-center">ID sản phẩm</th>
                    <th class="text-center">Sản phẩm</th>
                    <th class="text-center">Đơn giá</th>
                    <th class="text-center">Số lượng </th>
                    <th class="text-center">Thành tiền</th>
                </tr>
                @php $index = 1; @endphp
                @foreach ($report as $item)
                    <tr>
                        <td class="text-center">{{ $index++ }}</td>
                        <td class="text-center"><a href="#">#HĐ{{ $item->invoice_id }}</a></td>
                        <td class="text-center"><strong>{{$item->product_id}}</strong></td>
                        <td>
                            @switch($item->product_type)
                                @case('phone')
                                    <span class="label label-info">Điện thoại</span>
                                    @break
                                @case('fit')
                                    <span class="label label-success">Phụ kiện</span>
                                    @break
                                @case('accessory')
                                    <span class="label label-warning">Linh kiện</span>
                                    @break
                                @default

                            @endswitch
                            {{ $item->product->name }}
                        </td>
                        <td class="text-right">{{ format_price($item->price) }}</td>
                        <td class="text-center">{{ $item->quantity }}</td>
                        <td class="text-right">{{ format_price($item->price * $item->quantity) }}</td>
                    </tr>
                @endforeach
            </tbody>
        </table>

    </div>

@endsection

Kết quả thu được:

demo-poly-2

Chúc các bạn thành công!

Demo: https://github.com/phongtrank55/demo-post/tree/poly-relation

Tham khảo: https://laravel.com/docs/5.8/eloquent-relationships

default_image
Tác giả: Phan Sỹ Tân
ADMIN

Bình luận

Để lại bình luận

Email và số điện thoại sẽ không được công khai. Những trường bắt buộc được đánh dấu *

Repository deleted Your repository has remove
Loading
Bạn cần hỗ trợ?