You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 09:43:38