
本文介绍如何借助第三方扩展包 eloquent-has-many-deep,在 laravel 中优雅地建立 version ↔ function ↔ category 的多对多深层关联,实现 $version->categories 和 $category->versions 的直接访问。
在标准 Laravel Eloquent 关系中,Version 与 Category 并无直接表结构连接,而是通过中间模型 Function(含 category_id)和关联表 function_version 间接关联。这种「跨两层中间模型」的关系无法用原生 hasManyThrough 实现(该方法仅支持单层中间表),必须借助社区成熟方案。
推荐使用 staudenmeir/eloquent-has-many-deep 扩展包,它专为解决深层、多对多、混合关系而设计,API 简洁且兼容 Laravel 8+。
✅ 安装与基础配置
composer require staudenmeir/eloquent-has-many-deep:"^1.17"
⚠️ 注意:Laravel 9/10 用户请使用 ^2.0 版本;Laravel 11+ 推荐 ^3.0(需 PHP 8.1+)。安装后无需发布配置或服务提供者,开箱即用。
✅ 模型关系定义
假设模型已正确设置基础关系:
// app/Models/Version.php
use Illuminate\Database\Eloquent\Model;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
class Version extends Model
{
use HasRelationships;
// Version → (function_version) → Function → (belongs to) → Category
public function categories()
{
return $this->hasManyDeep(
Category::class,
['function_version', Function::class],
['version_id', 'id'], // 外键:function_version.version_id, functions.id
['id', 'category_id'] // 本地键:functions.id, functions.category_id
);
}
}// app/Models/Category.php
use Illuminate\Database\Eloquent\Model;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
class Category extends Model
{
use HasRelationships;
// Category → (belongs to) → Function → (function_version) → Version
public function versions()
{
return $this->hasManyDeep(
Version::class,
['functions', 'function_version'],
['id', 'function_id'], // 外键:functions.id, function_version.function_id
['category_id', 'id'] // 本地键:functions.category_id, function_version.id
);
}
}? 关键参数说明(以 Version::categories() 为例):
- 第1参数:目标模型 Category::class
- 第2参数:关联路径中的中间表/模型数组 —— 先经 function_version 表,再经 Function::class 模型
- 第3参数($foreignKeys):各中间段的外键字段名(即“指向下一模型”的字段)
- 第4参数($localKeys):各中间段的本地主键或关联字段名(即“被上一模型引用”的字段)
✅ 使用示例
// 获取某版本关联的所有分类(自动 JOIN + 去重)
$version = Version::find(1);
foreach ($version->categories as $category) {
echo $category->name . "\n";
}
// 获取某分类关联的所有版本
$category = Category::find(5);
$versions = $category->versions()->orderBy('name')->get();你甚至可以链式调用约束:
$version->categories()->where('categories.name', 'like', '%api%')->get();⚠️ 注意事项与最佳实践
- ✅ 性能提示:hasManyDeep 默认使用 JOIN 查询,避免 N+1;如需懒加载,请显式调用 load('categories')。
- ✅ 去重保障:当存在多条路径到达同一目标记录时(如一个 Category 下多个 Function 都关联同一 Version),结果会自动 DISTINCT。
- ❌ 不支持反向 belongsToDeep:若需 $function->version 这类单向反查,请优先考虑原生 belongsToMany 或自定义查询。
- ? 迁移一致性:确保 function_version 表有复合唯一索引 UNIQUE(function_id, version_id),防止重复绑定。
✅ 总结
通过 eloquent-has-many-deep,我们绕过了手动编写子查询或 DB::raw 的复杂性,以声明式语法实现了清晰、可维护、可测试的深层关联。它不是“黑魔法”,而是对 Eloquent 关系能力的合理延伸——当你遇到 Version → Function → Category 这类典型业务建模场景时,它就是最专业、最轻量的解决方案。










