Laravel 10多层嵌套关联查询中检查is_enabled字段的最优方案
Laravel 10多层关联模型级联检查is_enabled的最优方案
针对你提出的多层关联模型检查is_enabled字段的问题,不需要完全依赖深度嵌套whereHas,可以根据场景选择以下几种更高效的实现方式:
一、嵌套whereHas + 预加载过滤(基础场景)
如果只是临时需要针对特定查询做过滤,你可以在whereHas中通过嵌套闭包逐层检查关联模型的is_enabled,同时在预加载(with)时也对每层关联做过滤,确保返回的关联数据都是启用状态的:
$app = Application::with([ 'modelable', 'pingtree_group' => function ($query) { $query->where('is_enabled', true) ->with([ 'pingtree_group_entries' => function ($q) { $q->where('is_enabled', true) ->with([ 'pingtree' => function ($p) { $p->with([ 'pingtree_entries' => function ($pe) { $pe->with([ 'buyer_tier' => function ($bt) { $bt->where('is_enabled', true); } ]); } ]); } ]); } ]); } ]) ->where('id', 30) ->whereHas('pingtree_group', function ($query) { $query->where('is_enabled', true) ->whereHas('pingtree_group_entries', function ($q) { $q->where('is_enabled', true) ->whereHas('pingtree', function ($p) { $p->whereHas('pingtree_entries', function ($pe) { $pe->whereHas('buyer_tier', function ($bt) { $bt->where('is_enabled', true); }); }); }); }); }) ->first();
这里的whereHas用于确保主模型(Application)存在符合所有启用条件的关联链,而with中的过滤则保证预加载的关联数据本身都是启用状态,不会带回禁用的记录。
二、全局作用域(推荐,减少重复代码)
如果多个模型都包含is_enabled字段,且大部分场景下只需要查询启用状态的记录,全局作用域是最优解——一次性配置,所有查询自动生效:
1. 创建全局作用域类
<?php namespace App\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; class EnabledScope implements Scope { public function apply(Builder $builder, Model $model) { // 仅对包含is_enabled字段的模型生效 if (in_array('is_enabled', $model->getFillable()) || $model->hasColumn('is_enabled')) { $builder->where('is_enabled', true); } } }
2. 在目标模型中注册作用域
在PingtreeGroup、PingtreeGroupEntry、BuyerTier等需要过滤的模型的boot方法中添加:
protected static function boot() { parent::boot(); static::addGlobalScope(new \App\Scopes\EnabledScope); }
3. 简化查询代码
配置完成后,你的查询可以大幅简化,whereHas支持直接用点语法链式关联,全局作用域会自动帮你添加is_enabled=true的条件:
$app = Application::with([ 'modelable', 'pingtree_group.pingtree_group_entries.pingtree.pingtree_entries.buyer_tier' ])->where('id', 30) ->whereHas('pingtree_group.pingtree_group_entries.pingtree.pingtree_entries.buyer_tier') ->first();
如果偶尔需要查询禁用的记录,只需临时取消作用域即可:
PingtreeGroup::withoutGlobalScope(\App\Scopes\EnabledScope::class)->get();
三、自定义关联方法(特定场景复用)
如果某些关联路径需要频繁检查is_enabled,可以在模型中定义带过滤逻辑的关联方法,让代码更具语义化:
比如在PingtreeGroup模型中:
public function enabledEntries() { return $this->hasMany(PingtreeGroupEntry::class)->where('is_enabled', true); }
在PingtreeGroupEntry模型中:
public function enabledPingtree() { return $this->belongsTo(Pingtree::class)->where('is_enabled', true); }
以此类推定义后续的enabledEntries、enabledBuyerTier关联,之后查询时直接使用这些自定义关联:
$app = Application::with([ 'modelable', 'pingtree_group.enabledEntries.enabledPingtree.enabledEntries.enabledBuyerTier' ])->where('id', 30) ->whereHas('pingtree_group.enabledEntries.enabledPingtree.enabledEntries.enabledBuyerTier') ->first();
总结
- 全局作用域适合大部分场景,减少重复代码,维护成本最低;
- 嵌套
whereHas+预加载过滤适合临时的、特殊的查询需求; - 自定义关联方法适合需要频繁复用特定关联过滤逻辑的场景。
内容的提问来源于stack exchange,提问作者Ryan H
相关产品推荐
相关产品推荐

