You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Eloquent模型关系结构设计咨询:生日折扣特殊字段存储方案

Laravel 模型关系设计方案:处理带专属字段的餐厅-折扣多对多场景

核心思路

拒绝将生日折扣的专属字段塞进通用折扣表或中间表JSON,采用「通用折扣表 + 专属折扣详情表 + 关联关系」的结构,既保留原有的餐厅-折扣多对多关联,又能独立存储不同折扣类型的专属字段,同时避免数据冗余、提升扩展性。

数据库表结构调整

需要调整/新增以下表:

  1. restaurants:保持原有结构不变
  2. discounts:存储通用折扣信息,新增多态关联字段标记折扣类型
  3. birthdays:单独存储生日折扣专属字段,与discounts表关联
  4. restaurant_discount:保留原中间表结构,继续存储info等通用关联字段

迁移文件示例

// 创建通用折扣表
Schema::create('discounts', function (Blueprint $table) {
    $table->id();
    $table->string('name'); // 折扣名称
    $table->decimal('base_discount')->nullable(); // 通用折扣比例(示例)
    // 多态关联字段,关联具体折扣类型的详情表
    $table->morphs('discountable');
    $table->timestamps();
});

// 创建生日折扣专属表
Schema::create('birthdays', function (Blueprint $table) {
    $table->id();
    $table->boolean('free_kitchen'); // 是否赠送生日餐
    $table->boolean('alcohol_discount'); // 是否有酒水折扣
    $table->decimal('alcohol_percent'); // 酒水折扣百分比
    // 其他专属字段...
    $table->timestamps();
});

// 中间表保持原有结构
Schema::create('restaurant_discount', function (Blueprint $table) {
    $table->foreignId('restaurant_id')->constrained()->onDelete('cascade');
    $table->foreignId('discount_id')->constrained()->onDelete('cascade');
    $table->text('info'); // 通用关联信息(如使用规则)
    $table->primary(['restaurant_id', 'discount_id']);
});

模型关系定义

Restaurant 模型

保留原多对多关系,新增快捷方法直接获取生日折扣:

class Restaurant extends Model
{
    public function discounts(): BelongsToMany
    {
        return $this->belongsToMany(Discount::class)->withPivot('info');
    }

    // 快捷获取当前餐厅的所有生日折扣(含专属详情)
    public function birthdayDiscounts()
    {
        return $this->discounts()
            ->where('discountable_type', Birthday::class)
            ->with('discountable');
    }
}

Discount 模型

定义多态关联,关联到具体折扣类型的详情表:

class Discount extends Model
{
    public function restaurants(): BelongsToMany
    {
        return $this->belongsToMany(Restaurant::class);
    }

    // 多态关联:关联到具体折扣类型(如Birthday)
    public function discountable(): MorphTo
    {
        return $this->morphTo();
    }
}

Birthday 模型

单独存储专属字段,反向关联通用折扣表:

class Birthday extends Model
{
    protected $fillable = [
        'free_kitchen',
        'alcohol_discount',
        'alcohol_percent',
        // 其他专属字段
    ];

    // 反向多态关联:对应到通用折扣记录
    public function discount(): MorphOne
    {
        return $this->morphOne(Discount::class, 'discountable');
    }
}

使用示例

创建生日折扣并关联餐厅

// 1. 创建生日折扣专属详情
$birthdayDetail = Birthday::create([
    'free_kitchen' => true,
    'alcohol_discount' => true,
    'alcohol_percent' => 20,
]);

// 2. 创建通用折扣记录并关联详情
$discount = Discount::create([
    'name' => '生日专属福利',
    'base_discount' => 0.1,
    'discountable_id' => $birthdayDetail->id,
    'discountable_type' => Birthday::class,
]);

// 3. 关联到目标餐厅
$restaurant = Restaurant::find(1);
$restaurant->discounts()->attach($discount->id, ['info' => '仅限生日当天到店使用']);

获取餐厅的生日折扣

// 获取所有生日折扣(含专属详情和中间表信息)
$birthdayDiscounts = $restaurant->birthdayDiscounts()->get();

// 遍历输出示例
foreach ($birthdayDiscounts as $discount) {
    echo "折扣名称:{$discount->name}\n";
    echo "酒水折扣比例:{$discount->discountable->alcohol_percent}%\n";
    echo "使用规则:{$discount->pivot->info}\n";
}

为什么不使用模型继承?

如果让Birthday继承Discount,本质是单表继承(STI),需要把所有生日专属字段加到discounts表中,会导致非生日折扣的记录出现大量空字段,造成数据冗余,完全违背你「避免字段冗余」的需求。而上述分离表+关联的方案,每个折扣类型的专属字段只存在于对应表中,数据结构更整洁、扩展性更强。

内容的提问来源于stack exchange,提问作者maximos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:53:27