Laravel Eloquent BelongsToMany关联中间表字段的全局作用域实现
基于中间表active字段的全局关联过滤实现方案
核心思路
自定义全局作用域,仅对BelongsToMany类型的关联生效,自动给查询添加中间表active=true的过滤条件,同时支持通过withoutGlobalScope移除过滤,满足默认过滤、按需获取全部数据的需求。
1. 定义全局作用域类
创建ActivePivotScope类,专门处理中间表active字段的过滤逻辑:
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class ActivePivotScope implements Scope { public function apply(Builder $builder, Model $model) { // 仅对BelongsToMany关联执行过滤 if ($builder->getQuery()->getRelation() instanceof BelongsToMany) { /** @var BelongsToMany $relation */ $relation = $builder->getQuery()->getRelation(); $builder->wherePivot($relation->getPivotAccessor(), 'active', true); } } public function remove(Builder $builder, Model $model) { if ($builder->getQuery()->getRelation() instanceof BelongsToMany) { /** @var BelongsToMany $relation */ $relation = $builder->getQuery()->getRelation(); $column = $relation->getPivotTable() . '.active'; $query = $builder->getQuery(); // 精准移除对应的where条件 foreach ($query->wheres as $key => $where) { if ($where['column'] === $column && $where['operator'] === '=' && $where['value'] === true) { unset($query->wheres[$key]); $query->wheres = array_values($query->wheres); break; } } } } }
2. 应用全局作用域
方式一:单个关联直接配置
在模型的关联定义中,直接给目标关联添加该全局作用域:
class Client extends Model { public function promotions(): BelongsToMany{ return $this->belongsToMany(Promotion::class) ->using(ClientPromotionPivot::class) ->addGlobalScope('active_promotions', new ActivePivotScope()); } }
方式二:Trait批量复用(多模型场景推荐)
创建Trait实现批量给指定关联添加过滤,适配多个模型存在类似需求的场景:
trait HasActivePivotRelations { protected static function booted() { // 子类可通过重写$activePivotRelations指定需要过滤的关联 $activeRelations = property_exists($this, 'activePivotRelations') ? $this->activePivotRelations : []; foreach ($activeRelations as $relationName) { static::{$relationName}() ->addGlobalScope('active_promotions', new ActivePivotScope()); } } }
在模型中使用Trait并指定需要过滤的关联:
class Client extends Model { use HasActivePivotRelations; // 指定需要应用active过滤的关联名称 protected $activePivotRelations = ['promotions']; public function promotions(): BelongsToMany{ return $this->belongsToMany(Promotion::class) ->using(ClientPromotionPivot::class); } }
3. 使用示例
- 默认获取过滤后的关联数据:
$activePromotions = $client->promotions;
- 移除过滤获取全部关联数据:
$allPromotions = $client->promotions() ->withoutGlobalScope('active_promotions') ->get();
方案优势
- 高复用性:单个作用域类可适配所有带
active字段的中间表关联 - 灵活性:支持单个关联单独配置,也支持多模型批量配置
- 符合需求:无需定义重复关联或重复编写
wherePivot,完美支持默认过滤、按需取消过滤的场景
内容的提问来源于stack exchange,提问作者dmikam
相关产品推荐
相关产品推荐

