Laravel使用whereHas实现多层关联非空筛选
需求场景
项目包含三类核心模型:countries(国家)、cities(城市)、shops(商铺),需要筛选并返回所有满足以下条件的城市:
- 城市存在关联商铺
- 关联商铺存在有效
datas(数据) - 商铺的员工关联数据不为
NULL
当前未做正确筛选的返回结果如下,其中id=13的城市无关联商铺,id=19的城市存在带员工的关联商铺:
[ { "id": 1, "created_at": "2022-06-02T06:07:31.000000Z", "updated_at": "2022-06-02T06:07:31.000000Z", "name": "Niue", "cities": [ { "id": 13, "created_at": "2022-06-02T06:07:44.000000Z", "updated_at": "2022-06-02T06:07:44.000000Z", "country_id": 1, "name": "North Michealbury", "shops": [] //empty }, { "id": 19, "created_at": "2022-06-02T06:07:44.000000Z", "updated_at": "2022-06-02T06:07:44.000000Z", "country_id": 1, "name": "Millsmouth", "shops": [ { "id": 1, "created_at": "2022-06-02T06:14:37.000000Z", "updated_at": "2022-06-02T06:14:37.000000Z", "city_id": 19, "name": "Roberts-Okuneva", "employees_count": 146 } ] } ] } ]
现有代码问题
当前控制器代码未实现正确筛选,目标是仅返回存在带员工商铺的城市,现有代码如下:
return Country::with(['cities','cities.shops'=>function($employee){ $employee->withCount('employees')->where('city_id',19); }])->whereHas('cities',function($city){ $city->where('cities.id',19); })->get();
现有代码存在两个核心问题:
- 硬编码
city_id=19,仅会查询指定id的城市,无法通用筛选所有符合条件的城市 - 仅做了一层
whereHas校验,没有嵌套校验商铺下的员工关联,也没有对预加载的关联数据做过滤,导致不符合条件的城市、商铺仍然会被返回
已定义模型关联
各模型的关联定义如下,关联关系本身配置正确:
- Country模型
public function Cities(){ return $this->hasMany(City::class); }
注:Laravel关联方法建议使用小驼峰命名,此处
Cities()首字母大写不符合框架约定,建议改为cities()避免关联匹配异常。
- City模型
public function shops(){ return $this->hasMany(Shop::class); }
- Shop模型
public function employees(){ return $this->belongsToMany(User::class,'shop_employees','shop_id','employee_id'); }
正确实现代码
多层关联筛选需要同时做两层处理:一是用嵌套whereHas逐层校验父模型是否存在符合条件的关联,过滤掉不符合要求的国家、城市;二是给预加载的关联加同样的筛选条件,避免返回无效的关联数据(比如空商铺、无员工的商铺)。
具体实现代码如下:
return Country::with([ // 预加载符合条件的城市 'cities' => function($cityQuery) { $cityQuery // 筛选存在符合条件商铺的城市 ->whereHas('shops', function($shopQuery) { $shopQuery // 筛选存在关联员工、且数据有效的商铺 ->whereHas('employees') ->whereNotNull('datas') ->withCount('employees'); }) // 预加载城市下符合条件的商铺 ->with([ 'shops' => function($shopQuery) { $shopQuery ->whereHas('employees') ->whereNotNull('datas') ->withCount('employees'); } ]); } ]) // 最外层过滤:仅返回存在符合条件城市的国家 ->whereHas('cities', function($cityQuery) { $cityQuery->whereHas('shops', function($shopQuery) { $shopQuery->whereHas('employees')->whereNotNull('datas'); }); }) ->get();
逻辑说明
- 嵌套
whereHas的校验逻辑为:国家下存在满足要求的城市 → 城市下存在满足要求的商铺 → 商铺存在绑定员工、且datas字段非空 - 预加载阶段加相同筛选条件,是因为
whereHas仅负责过滤父模型,不会自动过滤预加载的关联数据,不加这层筛选的话,不符合条件的城市、商铺仍然会出现在返回结果中 - 如果不需要统计员工数量,可以去掉
withCount('employees')部分
内容的提问来源于stack exchange,提问作者Ray Paras
相关产品推荐
相关产品推荐

