
本文介绍在 Laravel 中通过 belongsTo 和 hasMany 自关联组合,配合 Eloquent 访问器与集合操作,实现从 Product 模型一次性获取其主单位及全部子单位(如 “Piece” 及其子单位 “Dozen - 12”)的完整方案。
本文介绍在 laravel 中通过 `belongsto` 和 `hasmany` 自关联组合,配合 eloquent 访问器与集合操作,实现从 product 模型一次性获取其主单位及全部子单位(如 “piece” 及其子单位 “dozen - 12”)的完整方案。
在 Laravel 的 Eloquent ORM 中,标准的 belongsTo 关系仅支持单列外键绑定(如 unit_id → units.id),无法直接基于“主单位 ID + 子单位关联字段”双条件动态加载一组相关记录。但通过合理建模与组合关系,我们完全可以优雅地实现目标效果:每个 Product 加载其 base unit(自身 unit_id 对应的单位)及其所有以该单位为 base_unit_id 的子单位,并合并为扁平化的 units 集合。
✅ 正确建模:分层定义关系
首先,在模型中明确职责分离:
- Product 通过 unit_id 属于一个基础单位(baseUnit);
- Unit 表存在自关联:base_unit_id 指向自身 id,因此一个 Unit 可拥有多个 subUnits。
// app/Models/Product.php
class Product extends Model
{
public function baseUnit()
{
return $this->belongsTo(Unit::class, 'unit_id', 'id');
}
// 访问器:返回包含 base unit + 所有 sub units 的合并集合
public function getUnitsAttribute()
{
if (!$this->relationLoaded('baseUnit')) {
$this->load('baseUnit.subUnits');
}
$base = $this->baseUnit->replicate(); // 避免修改原始关系实例
$base->unsetRelation('subUnits'); // 清除嵌套关系,保持扁平结构
return $this->baseUnit->subUnits->concat($base)->values();
}
}
// app/Models/Unit.php
class Unit extends Model
{
public function subUnits()
{
return $this->hasMany(Unit::class, 'base_unit_id', 'id');
}
}
✅ 使用方式:一行代码完成预加载与格式化
无需手动调用 with() 多层嵌套,直接使用 with('baseUnit.subUnits') 预加载,再通过访问器自动合并:
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
$products = Product::with('baseUnit.subUnits')->get();
foreach ($products as $product) {
echo $product->name . " 使用单位:\n";
foreach ($product->units as $unit) {
echo "- {$unit->name} (x{$unit->multiplier})\n";
}
}
输出结构完全符合预期(扁平数组、含 base 和 sub units),且避免 N+1 查询:
[
{
"id": 1,
"name": "Product 1",
"unit_id": 1,
"cost": 10,
"price": 14,
"units": [
{ "id": 2, "name": "Dozen - 12", "multiplier": 12, "base_unit_id": 1 },
{ "id": 1, "name": "Piece", "multiplier": 1, "base_unit_id": null }
]
}
]
? 关键技巧说明:
- 使用 replicate() 克隆 base unit 实例,防止意外修改原始关系数据;
- unsetRelation() 确保 baseUnit 不携带 subUnits 嵌套结构,使最终 units 集合纯净扁平;
- concat() 合并子单位集合与 base unit,并用 values() 重置键名(避免索引错乱);
- 访问器 getUnitsAttribute 自动触发预加载校验(relationLoaded),兼顾懒加载与性能。
⚠️ 注意事项
- 数据库约束建议:为 units.base_unit_id 添加外键索引(INDEX (base_unit_id)),提升 subUnits() 查询效率;
- 若需按 multiplier 排序(如 base unit 在前),可在 getUnitsAttribute 中添加 ->sortByDesc('multiplier');
- 不推荐在 with() 中直接使用 units 访问器名(如 with('units')),因其非真实关系,应始终预加载底层关系 baseUnit.subUnits;
- 如需 API 输出时序列化该字段,请确保 units 不被 $hidden 或 $casts 干扰,必要时可覆写 toArray() 或使用 append(['units'])。
通过这种“关系分层 + 访问器聚合”的设计模式,你既能保持 Eloquent 关系的清晰性与可测试性,又能灵活输出业务所需的扁平化数据结构——这才是 Laravel 中处理自关联层级数据的最佳实践。










