该不该将搜索逻辑塞进 eloquent 的 where 链?不该——短期虽快,长期导致嵌套 if、orwhere 泛滥,可读性崩坏;应抽象为独立可复用单元,优先用 when()(3–4 个简单条件)或 pipeline(多字段、可选、需解耦与测试的场景),每个过滤器只专注一个职责,且必须返回 builder 实例以维持链路。

搜索逻辑该不该塞进 Eloquent 的 where 里
直接在 Model::query()->where(...) 里拼一堆条件,短期快,长期难维护。尤其当筛选字段多(比如 status、category_id、date_range、search_text)、且部分条件可选时,代码会迅速变成嵌套 if + 多重 orWhere,可读性崩坏,也容易漏掉 when() 的空值判断。
真正该做的是把“过滤行为”抽象成独立、可复用、可测试的单元——Pipeline 就是为此而生,不是炫技,是让搜索逻辑从控制器里彻底解耦。
- 用
when()是轻量级方案,适合 3–4 个简单条件;超过这个数,就该考虑 Pipeline - 每个过滤器只关心一个职责:比如
StatusFilter只管status字段,不碰category_id - 注意
request()->input('xxx')默认返回null,但when(null, ...)不会执行闭包,所以别手动判空再调where,交给when()或过滤器内部处理
Laravel Pipeline 怎么接上 Eloquent 查询构建器
Pipeline 本身不认 Builder,它只传入一个“管道输入”,你得确保每一步都返回同一个 Builder 实例,否则链路断掉。
关键点:所有过滤器必须接收 Builder 为第一个参数,并显式 return 它;不能只调 $builder->where(...) 就完事。
class NameFilter
{
public function handle($builder, Closure $next)
{
$name = request()->input('name');
if ($name) {
$builder->where('name', 'like', "%{$name}%");
}
return $next($builder); // ← 必须 return,且传回 $builder
}
}
- 不要在过滤器里写
return $builder->where(...)—— 这会跳过后续过滤器 - 别把
request()写死在过滤器里,不利于单元测试;推荐通过构造函数或handle第二个参数注入依赖 - 如果某个过滤器需提前终止(如权限拦截),可不调
$next(),直接 return 结果(比如空集合),但搜索场景极少需要
多个过滤器执行顺序会影响 SQL 效率吗
会影响,但不是因为 Pipeline 本身,而是你写的 where 条件是否能命中索引。Pipeline 只是顺序调用,最终生成的 SQL 还是靠 Eloquent 拼出来的,和手写 where 链无异。
真正要注意的是:高选择性条件(如带索引的 user_id)尽量往前放,低选择性(如 status IN ('draft','pending'))往后,让 MySQL 尽早缩小结果集。
-
where('deleted_at', null)这类“伪条件”如果没索引,放前面反而拖慢整体查询 - 日期范围(
created_at BETWEEN ? AND ?)建议给字段加联合索引,尤其是和高频筛选字段组合(如(status, created_at)) - 全文搜索(
LIKE '%xxx%')无法走索引,别指望靠调整 Pipeline 顺序优化它;真要搜文本,该上fulltext或Meilisearch
为什么写了 Pipeline 却没生效,查不到数据
最常见原因是:忘了在控制器里真正“跑”这个 pipeline。写了一堆过滤器,却还是直接 Model::query()->get(),Pipeline 根本没被调用。
另一个高频坑:过滤器里用了 request()->xxx,但请求是 POST 且没开 VerifyCsrfToken 白名单,或者前端传参 key 名和后端 expect 的不一致(比如前端传 filter[status],后端却读 request()->input('status'))。
- 检查是否调用了
app(Pipeline::class)->send($builder)->through([...])->then(...) - 在过滤器开头加
Log::debug('NameFilter triggered', ['input' => request()->all()]);确认是否进入 - 用
DB::enableQueryLog()+dd(DB::getQueryLog())看最终生成的 SQL 是否含预期条件 - 注意
when()和 Pipeline 不冲突,可以混用;但别在一个地方既用when()又在 Pipeline 里重复处理同一字段









