在Laravel项目中,经常会遇到用户和组织存在多层级从属关系的场景,比如用户属于子组织,子组织又从属于上级组织,此时需要获取用户所属的所有层级组织下的事件,普通的单层级关联查询无法覆盖全部需求,需要采用更灵活的方案实现。

基础关联模型定义
首先我们需要定义对应的数据模型,假设存在User(用户)、Organization(组织)、Event(事件)三个模型,它们的关联关系如下:
- 用户属于一个组织,
User模型定义organization关联 - 组织可以有上级组织,
Organization模型定义parent和children关联 - 组织可以拥有多个事件,
Organization模型定义events关联
对应的模型基础代码如下:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class User extends Model
{
// 用户所属组织关联
public function organization()
{
return $this->belongsTo(Organization::class);
}
}
class Organization extends Model
{
// 组织的上级组织关联
public function parent()
{
return $this->belongsTo(Organization::class, 'parent_id');
}
// 组织的下级组织关联
public function children()
{
return $this->hasMany(Organization::class, 'parent_id');
}
// 组织拥有的事件关联
public function events()
{
return $this->hasMany(Event::class);
}
// 递归获取所有上级组织
public function getAllParents()
{
$parents = collect();
$current = $this->parent;
while ($current) {
$parents->push($current);
$current = $current->parent;
}
return $parents;
}
// 递归获取所有下级组织
public function getAllChildren()
{
$children = collect();
foreach ($this->children as $child) {
$children->push($child);
$children = $children->merge($child->getAllChildren());
}
return $children;
}
}
class Event extends Model
{
// 事件所属组织关联
public function organization()
{
return $this->belongsTo(Organization::class);
}
}
方案一:递归获取所有关联组织ID再查询事件
这种方式先通过递归逻辑获取用户所属组织以及所有层级的上级、下级组织ID,再通过事件表的organization_id字段匹配查询所有事件,逻辑清晰,适合层级不深的场景。
具体实现代码如下:
<?php
namespace AppServices;
use AppModelsUser;
use AppModelsOrganization;
class EventService
{
public function getUserAllOrgEvents(User $user)
{
// 获取用户所属组织
$userOrg = $user->organization;
if (!$userOrg) {
return collect();
}
// 收集所有关联组织ID
$orgIds = collect([$userOrg->id]);
// 添加所有上级组织ID
$parentOrgs = $userOrg->getAllParents();
$orgIds = $orgIds->merge($parentOrgs->pluck('id'));
// 添加所有下级组织ID
$childOrgs = $userOrg->getAllChildren();
$orgIds = $orgIds->merge($childOrgs->pluck('id'));
// 去重后查询事件
$orgIds = $orgIds->unique()->toArray();
return AppModelsEvent::whereIn('organization_id', $orgIds)->get();
}
}
方案二:使用Eloquent的with嵌套关联查询
如果组织的层级深度固定,或者只需要查询特定方向的层级(比如仅向上查询所有上级组织),可以使用Eloquent的with嵌套关联,减少手动递归的代码量,Laravel会自动处理关联加载。
比如仅查询用户所属组织及其所有上级组织的事件,实现代码如下:
<?php
namespace AppServices;
use AppModelsUser;
class EventService
{
public function getUserUpwardOrgEvents(User $user)
{
// 预加载用户所属组织、组织的上级、上级的事件
$user = $user->load('organization.parent.events');
// 如果层级更深,可以继续嵌套parent.parent.events,或者自定义递归关联
$events = collect();
$org = $user->organization;
while ($org) {
$events = $events->merge($org->events);
$org = $org->parent;
}
return $events;
}
}
方案三:缓存优化高频查询场景
如果用户的组织层级和事件数据变化不频繁,但是查询频率很高,可以给关联查询结果添加缓存,减少数据库查询次数,提升性能。
缓存实现示例如下:
<?php
namespace AppServices;
use AppModelsUser;
use IlluminateSupportFacadesCache;
class EventService
{
public function getUserAllOrgEventsWithCache(User $user, $cacheMinutes = 60)
{
$cacheKey = 'user_' . $user->id . '_all_org_events';
return Cache::remember($cacheKey, $cacheMinutes, function () use ($user) {
// 这里调用方案一中的查询逻辑
$userOrg = $user->organization;
if (!$userOrg) {
return collect();
}
$orgIds = collect([$userOrg->id]);
$parentOrgs = $userOrg->getAllParents();
$orgIds = $orgIds->merge($parentOrgs->pluck('id'));
$childOrgs = $userOrg->getAllChildren();
$orgIds = $orgIds->merge($childOrgs->pluck('id'));
$orgIds = $orgIds->unique()->toArray();
return AppModelsEvent::whereIn('organization_id', $orgIds)->get();
});
}
}
不同方案对比
三种方案的适用场景和优缺点如下:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 递归收集ID查询 | 层级深度不固定,需要全层级覆盖 | 逻辑通用,覆盖所有层级关系 | 层级过深时递归次数多,性能略低 |
| Eloquent嵌套关联 | 层级深度固定,或仅需特定方向层级 | 代码简洁,利用Eloquent原生能力 | 层级不固定时需要额外处理,灵活性不足 |
| 缓存优化方案 | 高频查询,数据更新频率低 | 性能提升明显,减少数据库压力 | 需要处理缓存失效逻辑,增加复杂度 |
注意事项
- 递归获取层级时,要避免组织层级出现循环引用,否则会导致无限递归,可以在递归逻辑中添加已访问ID判断,防止死循环
- 如果组织层级过深,建议限制递归最大深度,防止查询耗时过长
- 事件数据较多时,查询可以按需添加分页、排序、筛选条件,避免一次性加载过多数据