如何将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
相关产品推荐
相关产品推荐

