Eloquent模型关系结构设计咨询:生日折扣特殊字段存储方案
Laravel 模型关系设计方案:处理带专属字段的餐厅-折扣多对多场景
核心思路
拒绝将生日折扣的专属字段塞进通用折扣表或中间表JSON,采用「通用折扣表 + 专属折扣详情表 + 关联关系」的结构,既保留原有的餐厅-折扣多对多关联,又能独立存储不同折扣类型的专属字段,同时避免数据冗余、提升扩展性。
数据库表结构调整
需要调整/新增以下表:
restaurants:保持原有结构不变discounts:存储通用折扣信息,新增多态关联字段标记折扣类型birthdays:单独存储生日折扣专属字段,与discounts表关联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
相关产品推荐
相关产品推荐

