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

Laravel 8 如何按父模型布尔开关条件加载关联模型

问题原因
  • 误用whereHas方法:该方法作用是过滤主表记录,仅返回存在匹配关联的Domain数据,会直接把certificate_checks_enabled=false的Domain从结果中剔除,和「返回所有符合条件的Domain、仅部分记录挂载关联」的需求完全冲突。
  • 关联条件逻辑错误:certificate_checks_enabled是Domain表的字段,原有代码将其写在Certificate表的查询条件中,永远无法生效;同时whereHas闭包内多余的->get()调用会破坏查询构造器的链式逻辑。
  • 搜索条件分组存在语法隐患:闭包内第一个条件使用orWhere,会生成带前导OR的分组SQL,触发语法错误,需要将第一个orWhere改为where。
正确实现方案

使用条件预加载替换原有的whereHas逻辑,通过关联反向约束筛选需要加载的Certificate数据,全程仅产生2条SQL,无N+1性能问题:

$domains = Domain::where('user_id', Auth::id())
    ->where(function ($sql) use ($search) {
        // 第一个条件改为where,避免SQL语法错误
        $sql->where('domain', 'like', "$search%")
            ->orWhere('status', 'like', "$search%")
            ->orWhere('registrar', 'like', "$search%");
    })
    // 条件预加载关联,仅给开启了证书检查的Domain挂载Certificate
    ->with(['certificate' => function ($query) {
        // 前提:Certificate模型中需要定义反向关联domain(),返回$this->belongsTo(Domain::class)
        $query->whereHas('domain', function ($subQuery) {
            $subQuery->where('certificate_checks_enabled', true);
        });
    }])
    ->orderBy($sort[0] ?? 'expires_on', $sort[1] ?? 'desc')
    ->paginate((int) $perPage);

如果不想定义Certificate的反向关联,也可以用原生SQL实现相同的预加载约束:

->with(['certificate' => function ($query) {
    $query->whereRaw('EXISTS (
        SELECT 1 FROM domains 
        WHERE domains.id = certificates.domain_id 
        AND domains.certificate_checks_enabled = 1
    )');
}])
实现效果

修改后主查询会返回所有符合用户ID、搜索条件的Domain分页记录,预加载阶段仅查询对应Domain开启了证书检查的Certificate数据,Laravel会自动将匹配的Certificate模型挂载到对应Domain实例上,未开启证书检查的Domain实例的certificate关联值为null,完全符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 01:33:19