
本文介绍在 Laravel 中处理“一个产品关联基础单位,而该基础单位又拥有多个子单位”的场景,通过分层 Eloquent 关系(belongsTo + hasMany)结合模型辅助方法,将基础单位与子单位统一聚合为扁平化 units 集合,满足前端一体化渲染需求。
本文介绍在 laravel 中处理“一个产品关联基础单位,而该基础单位又拥有多个子单位”的场景,通过分层 eloquent 关系(belongsto + hasmany)结合模型辅助方法,将基础单位与子单位统一聚合为扁平化 `units` 集合,满足前端一体化渲染需求。
在实际业务中,常遇到单位体系的层级结构:例如「Piece(件)」是基础单位,「Dozen(一打 = 12 件)」是其子单位,二者通过 base_unit_id 关联。当一个 Product 关联到基础单位(如 unit_id = 1),我们期望一次性加载该基础单位及其所有子单位,并以扁平数组形式返回(而非嵌套结构),便于前端统一处理。
Laravel 原生的 belongsTo 不支持基于「两个条件」(如 unit_id = ? OR base_unit_id = ?)动态匹配多条记录,因此不能直接用单个关系实现“获取所有相关单位”。正确解法是分层建模 + 应用层聚合:
✅ 步骤一:正确定义 Eloquent 关系
在 Product.php 模型中定义基础单位关系:
// app/Models/Product.php
public function baseUnit()
{
return $this->belongsTo(Unit::class, 'unit_id', 'id');
}
在 Unit.php 模型中定义子单位关系(自关联 hasMany):
// app/Models/Unit.php
public function subUnits()
{
return $this->hasMany(Unit::class, 'base_unit_id', 'id');
}
这样即可通过预加载高效获取完整层级:
$products = Product::with(['baseUnit.subUnits'])->get();
输出结构为嵌套形式(base_unit → sub_units),符合数据库范式,但不符合前端期望的扁平 units 数组。
✅ 步骤二:添加聚合访问器(Accessor)
在 Product.php 中新增 units 访问器,将基础单位与子单位合并为 Collection:
// app/Models/Product.php
public function units()
{
// 防止 N+1 查询,确保 baseUnit 已加载
if (!$this->relationLoaded('baseUnit')) {
$this->load('baseUnit.subUnits');
}
$baseUnit = $this->baseUnit->replicate(); // 克隆避免污染原始关系
$baseUnit->unsetRelation('subUnits'); // 移除子单位关系,保持扁平
return $this->baseUnit->subUnits->merge($baseUnit)->values();
}
? 提示:merge() 合并后顺序为「子单位在前、基础单位在后」;若需基础单位优先,可调换顺序:$baseUnit->subUnits->prepend($baseUnit)->values()
✅ 步骤三:使用方式(支持 Eager Loading + API 输出)
虽然 units 是访问器(非关系),无法直接 with('units'),但可通过 load() 显式加载依赖关系:
$products = Product::with('baseUnit.subUnits')->get();
// 在 API 资源或 toArray() 中使用
return $products->map(function ($product) {
return [
'id' => $product->id,
'name' => $product->name,
'unit_id' => $product->unit_id,
'cost' => $product->cost,
'price' => $product->price,
'units' => $product->units, // 触发聚合逻辑
];
});
或在 ProductResource 中统一处理:
// app/Http/Resources/ProductResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'unit_id' => $this->unit_id,
'cost' => $this->cost,
'price' => $this->price,
'units' => UnitResource::collection($this->units),
];
}
⚠️ 注意事项
- 性能敏感场景慎用 replicate():若单位数据量极大,可改用 makeVisible() 或手动构造新实例;
- 空子单位兼容性:$this->baseUnit->subUnits 在无子单位时为空 Collection,merge() 仍安全;
- 避免循环引用:确保 Unit 模型未意外定义反向 baseUnit() 关系导致序列化异常;
- 数据库约束建议:为 base_unit_id 添加外键索引(INDEX (base_unit_id)),提升子单位查询效率。
通过分层关系建模 + 应用层聚合,既保持了 Eloquent 的可维护性与查询效率,又灵活满足了业务侧对扁平化数据结构的需求——这是 Laravel 处理复合关联关系的经典实践模式。











