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

如何将Laravel Task模型的Analogues Scope转为可预加载的关联关系?

问题:将Scope转为可预加载的Eloquent关联

我在Task模型中定义了scopeAnalogues方法,用于筛选同类任务:

public function scopeAnalogues(Builder $builder, Task $task): Builder
{
    $themeIds = $task
        ->themes
        ->pluck('id')
        ->toArray();

    return $builder
        ->where('id', '!=', $task->id)
        ->where('task_line_id', $task->task_line_id)
        ->whereHas('themes', function (Builder $builder) use ($themeIds) {
            $builder->whereIn('themes.id', $themeIds);
        });
}

现在我希望通过with('analogues')预加载这些同类任务(已知parent_id递归关联的实现方式,但本次场景不同),尝试编写了如下HasMany关联方法,但无法生效——因为关联上下文里$this的行为不符合预期:

public function analogues(): HasMany
{
    return $this->hasMany(Task::class, 'id')
        ->where('id', '!=', $this->id)
        ->whereHas('themes', function ($query) {
            $query->whereIn('themes.id', $this->themes->pluck('id')->toArray());
        });
}

解决方案

原关联方法失效的核心原因:

  • 关联定义中直接调用$this->themes会触发额外的单例查询,预加载场景下会导致N+1问题
  • 预加载时Eloquent会批量处理所有父模型,此时依赖实例属性的查询逻辑无法生成统一的批量查询语句

方法1:基于数据库关联的关联定义(推荐,适合预加载)

通过whereExists和子查询实现数据库层面的关联匹配,避免依赖模型实例的已加载属性:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB;

public function analogues(): HasMany
{
    return $this->hasMany(Task::class)
        ->where('task_line_id', $this->task_line_id)
        ->where('id', '!=', $this->getKey())
        ->whereExists(function (Builder $query) {
            // 假设任务与主题的中间表名为task_theme,字段为task_id和theme_id
            $query->select(DB::raw(1))
                ->from('task_theme')
                ->whereColumn('task_theme.task_id', 'tasks.id')
                ->whereIn('task_theme.theme_id', function (Builder $subQuery) {
                    $subQuery->select('theme_id')
                        ->from('task_theme')
                        ->whereColumn('task_theme.task_id', $this->getKey());
                });
        });
}

方法2:复用现有Scope

利用tap方法在关联查询中复用已有的scopeAnalogues,适合快速迭代,但预加载时会触发N+1(每个模型单独执行关联查询):

public function analogues(): HasMany
{
    return $this->hasMany(Task::class)
        ->tap(function (Builder $query) {
            $this->scopeAnalogues($query, $this);
        });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 11:47:45