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

Laravel全局作用域中能否使用模型属性?权限控制场景求助

解决Laravel全局作用域中无法获取模型属性的问题(限制Encounter模型访问权限)

问题背景

我想限制Encounter模型仅对特定用户组开放,同时屏蔽组内禁止的服务类型,打算用全局作用域实现,但代码里用$this->service_type_id报错,因为全局作用域里拿不到模型属性。

当前错误代码:

protected static function boot()
{
    parent::boot();
    static::addGlobalScope('status', function (Builder $builder) {
      $builder->whereHas('groups.users', fn($q) => $q->where('user_id', Auth::user()->id))
        ->whereDoesntHave('groups.prohibitedServiceTypes', fn($q) => $q->where('service_type_id', $this->service_type_id));
    });
}

关联关系:Encounter和Groups是多对多,Groups和Users、ProhibitedServiceTypes也是多对多。目标是筛选出当前用户所在组关联的、且service_type_id不在组禁止列表中的Encounter,对应的原生SQL如下:

SELECT * FROM encounters e 
JOIN group_encounter ge ON (e.id = ge.encounter_id) 
JOIN groups g ON (g.id=ge.group_id)
JOIN group_user gu ON (gu.group_id=g.id)
WHERE gu.user_id = <current user> AND NOT EXISTS 
   (SELECT * FROM group_prohibited_service_types WHERE service_type_id = <current service type>)

问题根源

全局作用域是在构建查询阶段执行的,此时还没有生成具体的Encounter模型实例,所以$this根本不存在,自然拿不到service_type_id属性。要实现字段之间的关联对比,得用whereColumn来关联主表和关联表的字段。


解决方案

方案一:用Eloquent关联方法(推荐,符合Laravel规范)

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;

protected static function boot()
{
    parent::boot();

    static::addGlobalScope('accessible', function (Builder $builder) {
        // 未登录用户直接返回空结果
        if (!Auth::check()) {
            $builder->whereRaw('1=0');
            return;
        }

        $userId = Auth::user()->id;
        // 动态获取Encounter表名,避免硬编码
        $encounterTable = $builder->getModel()->getTable();

        $builder
            // 筛选当前用户所在组关联的Encounter
            ->whereHas('groups', function ($groupQuery) use ($userId) {
                $groupQuery->whereHas('users', fn($userQ) => $userQ->where('user_id', $userId));
            })
            // 排除那些所在组禁止了其service_type_id的Encounter
            ->whereDoesntHave('groups', function ($groupQuery) use ($encounterTable) {
                $groupQuery->whereHas('prohibitedServiceTypes', fn($prohibitedQ) => 
                    $prohibitedQ->whereColumn('service_type_id', "{$encounterTable}.service_type_id")
                );
            });
    });
}

方案二:贴近原生SQL的实现

如果更习惯原生SQL的逻辑,可以用join和whereNotExists来实现,和你给出的SQL几乎一致:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

protected static function boot()
{
    parent::boot();

    static::addGlobalScope('accessible', function (Builder $builder) {
        if (!Auth::check()) {
            $builder->whereRaw('1=0');
            return;
        }

        $userId = Auth::user()->id;
        $encounterTable = $builder->getModel()->getTable();

        $builder
            ->join('group_encounter', "{$encounterTable}.id", '=', 'group_encounter.encounter_id')
            ->join('groups', 'groups.id', '=', 'group_encounter.group_id')
            ->join('group_user', 'groups.id', '=', 'group_user.group_id')
            ->where('group_user.user_id', $userId)
            ->whereNotExists(function ($subquery) use ($encounterTable) {
                $subquery->select(DB::raw(1))
                    ->from('group_prohibited_service_types')
                    ->whereColumn('group_prohibited_service_types.service_type_id', "{$encounterTable}.service_type_id")
                    ->whereColumn('group_prohibited_service_types.group_id', 'groups.id');
            })
            // 确保只查询Encounter的字段,避免join带来的重复字段问题
            ->select("{$encounterTable}.*");
    });
}

关键注意事项

  1. 必须判断用户登录状态:未登录时Auth::user()为null,直接返回空结果避免报错。
  2. 动态获取表名:用$builder->getModel()->getTable()代替硬编码表名,符合Laravel的可维护性要求。
  3. 用whereColumn做字段对比:这是实现“当前Encounter的service_type_id不在组禁止列表”的核心,它会对比两个表的字段值,而不是传固定值。

内容的提问来源于stack exchange,提问作者mankowitz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:29:50