通过 Eloquent 关联模型实现订单菜品分组
在开发餐饮或电商类应用时,订单系统是核心模块。一个订单通常包含多种菜品或商品,如何高效地组织、查询和展示这些数据,是开发者面临的一个常见挑战。Laravel 的 Eloquent ORM 提供了强大而优雅的关系映射功能,能够帮助我们清晰地构建数据模型并实现复杂的数据操作。本文将深入探讨如何利用 Eloquent 的关联模型,特别是<belongsToMany>和<hasManyThrough>等关系,来实现订单中菜品的逻辑分组与聚合查询。
一、数据模型设计
首先,我们需要定义核心的数据表结构。一个典型的订单-菜品系统至少包含以下三张表:
orders:存储订单主信息,如订单号、用户ID、总金额、状态等。
dishes:存储菜品信息,如名称、价格、描述、分类ID等。
order_dish:作为订单和菜品之间的中间表(Pivot Table),存储关联关系及订单项的具体信息,如购买数量、下单时的单价等。
此外,为了实现分组(例如按菜品分类分组),我们通常还需要一个dish_categories表来存储分类信息。
1.1 创建模型与迁移文件
我们使用 Artisan 命令来生成模型和对应的迁移文件。
php artisan make:model Order -m php artisan make:model Dish -m php artisan make:model DishCategory -m
对于中间表order_dish,我们通常不需要为其创建模型(除非有特殊需求),只需创建迁移文件。
php artisan make:migration create_order_dish_table
1.2 编写迁移文件
以下是核心迁移文件的简化示例。
// database/migrations/xxxx_create_dish_categories_table.php
public function up()
{
Schema::create('dish_categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
// database/migrations/xxxx_create_dishes_table.php
public function up()
{
Schema::create('dishes', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained('dish_categories');
$table->string('name');
$table->decimal('price', 8, 2);
$table->timestamps();
});
}
// database/migrations/xxxx_create_orders_table.php
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('order_no')->unique();
$table->decimal('total_amount', 10, 2);
$table->timestamps();
});
}
// database/migrations/xxxx_create_order_dish_table.php
public function up()
{
Schema::create('order_dish', function (Blueprint $table) {
$table->id();
$table->foreignId('order_id')->constrained()->onDelete('cascade');
$table->foreignId('dish_id')->constrained()->onDelete('cascade');
$table->integer('quantity');
$table->decimal('unit_price', 8, 2); // 下单时的价格,可能与菜品当前价格不同
$table->timestamps();
});
}二、定义 Eloquent 关联关系
模型间的关联关系是 Eloquent 的核心。正确的关联定义是实现高效查询和分组的基础。
2.1 Order 模型
一个订单拥有多个菜品,并且这种关系是多对多的,需要通过中间表order_dish来连接。
// app/Models/Order.php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsBelongsToMany;
class Order extends Model
{
// 定义与 Dish 模型的多对多关系
public function dishes(): BelongsToMany
{
return $this->belongsToMany(Dish::class, 'order_dish')
->using(OrderDish::class) // 如果使用了自定义Pivot模型
->withPivot('quantity', 'unit_price') // 获取中间表的额外字段
->withTimestamps(); // 如果中间表有时间戳
}
}2.2 Dish 模型
一个菜品属于一个分类,同时也可以属于多个订单。
// app/Models/Dish.php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsBelongsTo;
use IlluminateDatabaseEloquentRelationsBelongsToMany;
class Dish extends Model
{
// 定义与 DishCategory 模型的归属关系
public function category(): BelongsTo
{
return $this->belongsTo(DishCategory::class);
}
// 定义与 Order 模型的多对多关系
public function orders(): BelongsToMany
{
return $this->belongsToMany(Order::class, 'order_dish')
->withPivot('quantity', 'unit_price')
->withTimestamps();
}
}2.3 DishCategory 模型
一个分类下拥有多个菜品。
// app/Models/DishCategory.php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsHasMany;
class DishCategory extends Model
{
// 定义与 Dish 模型的一对多关系
public function dishes(): HasMany
{
return $this->hasMany(Dish::class);
}
}2.4 (可选)自定义 Pivot 模型 OrderDish
如果需要对中间表进行更复杂的操作,可以为其创建一个 Pivot 模型。
// app/Models/OrderDish.php
namespace AppModels;
use IlluminateDatabaseEloquentRelationsPivot;
class OrderDish extends Pivot
{
// 如果需要,可以在这里定义属性或方法
protected $casts = [
'unit_price' => 'decimal:2',
];
}然后在Order模型的dishes()关联中使用->using(OrderDish::class)。