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

Laravel中使用whereHas时如何向关联函数传递参数

Laravel中向关联函数传递参数的问题及解决方案

问题描述

在Laravel开发中,我定义了带参数的模型关联方法(非查询回调):

  • UserRelationships模型包含接收Carbon日期参数的scopeAt动态作用域;
  • User模型的progenial_relation、parental_relation关联方法支持可选Carbon日期参数,直接调用这些关联并传递参数时可正常工作。

但使用whereHas、has这类预加载查询方法时,尝试多种语法均无法向关联函数传递参数。若在查询回调中重复调用scopeAt,生成的SQL会出现重复的日期约束,进而使查询失效。现咨询:是否存在可行的方式向关联函数传递参数?或有什么替代方案?

相关代码示例

UserRelationships模型

class UserRelationships extends Model
{
    public function scopeAt($query, Carbon $date)
    {
        return $query->where('superseded_at', '>', $date )
                    ->where('created_at', '<=', $date );
    }
}

User模型

class User extends Authenticatable
{
    public function progenial_relation(Carbon $date=null)
    {
        return $this->hasOne(UserRelationships::class, 'user_id', 'id')
                    ->at( @$date ?: Carbon::now() )
                    ->orderByDesc('created_at')
                    ->limit(1);
    }
    public function parental_relation(Carbon $date=null)
    {
        return $this->hasMany(UserRelationships::class, 'sponsor_id', 'id')
                    ->at( @$date ?: Carbon::now() );
    }

    // 尝试的无效写法
    public function children(Carbon $date=null)
    {
        $date = @$date ?: Carbon::now();
        return self::whereHas('progenial_relation', function($q) {
            $q->where('sponsor_id', $this->id);
        }, $date); // 不生效
    }

    // 导致重复约束的写法
    public function children(Carbon $date=null)
    {
        $date = @$date ?: Carbon::now();
        return self::whereHas('progenial_relation', function($q) use ($date) {
            $q->at($date)->where('sponsor_id', $this->id);
        });
    }
}

生成的错误SQL

select * from `users` 
where exists (
    select * 
    from `user_relationships` 
    where `users`.`id` = `user_relationships`.`user_id` 
    and `user_relationships`.`superseded_at` > ? 
    and `user_relationships`.`created_at` <= ? 
    and `sponsor_id` = ? 
    and `user_relationships`.`superseded_at` > ? 
    and `user_relationships`.`created_at` <= ?
)
and `users`.`deleted_at` is null

解决方案

方案1:拆分基础关联与带参数关联(推荐)

将关联关系与日期约束拆分,基础关联仅定义关联规则,带参数的关联方法在基础关联上添加约束,whereHas调用基础关联并在回调中添加日期条件:

class User extends Authenticatable
{
    // 基础关联:仅定义关联关系,不带任何约束
    public function progenial_relation_base()
    {
        return $this->hasOne(UserRelationships::class, 'user_id', 'id')
                    ->orderByDesc('created_at')
                    ->limit(1);
    }

    public function parental_relation_base()
    {
        return $this->hasMany(UserRelationships::class, 'sponsor_id', 'id');
    }

    // 保留原有的带参数关联方法,供直接调用使用
    public function progenial_relation(Carbon $date=null)
    {
        return $this->progenial_relation_base()->at($date ?: Carbon::now());
    }

    public function parental_relation(Carbon $date=null)
    {
        return $this->parental_relation_base()->at($date ?: Carbon::now());
    }

    // 正确的children查询方法
    public function children(Carbon $date=null)
    {
        $date = $date ?: Carbon::now();
        return self::whereHas('progenial_relation_base', function($q) use ($date) {
            $q->at($date)->where('sponsor_id', $this->id);
        });
    }
}

这种方式逻辑清晰,避免了重复添加约束的问题,同时保留了原关联方法的调用习惯。

方案2:自定义查询范围封装逻辑

在User模型中定义查询范围,将关联查询与日期约束的逻辑封装在一起:

class User extends Authenticatable
{
    // ... 保留原关联方法 ...

    public function scopeHasChildrenAt($query, Carbon $date=null)
    {
        $date = $date ?: Carbon::now();
        return $query->whereHas('progenial_relation_base', function($q) use ($date) {
            $q->at($date)->where('sponsor_id', $this->id);
        });
    }
}

// 使用示例
$user->hasChildrenAt($specificDate)->get();

方案3:利用闭包传递参数(Laravel 8+)

Laravel 8及以上版本支持通过闭包形式定义关联并传递参数,但写法相对繁琐:

class User extends Authenticatable
{
    public function progenial_relation(Carbon $date=null)
    {
        $date = $date ?: Carbon::now();
        return $this->hasOne(UserRelationships::class, 'user_id', 'id')
                    ->at($date)
                    ->orderByDesc('created_at')
                    ->limit(1);
    }

    public function children(Carbon $date=null)
    {
        $date = $date ?: Carbon::now();
        return self::whereHas(function($query) use ($date) {
            // 将关联的约束添加到查询中
            $this->progenial_relation($date)->addConstraintsToQuery($query);
        }, function($q) {
            $q->where('sponsor_id', $this->id);
        });
    }
}

原因分析

原写法中,whereHas会先调用progenial_relation关联方法,该方法已通过at($date)添加了日期约束;之后在回调中再次调用at($date),导致SQL中重复生成相同的日期条件,最终引发查询失效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 18:10:26