应将查询逻辑移至模型层:一、用本地作用域封装复用查询;二、用闭包分组处理OR条件;三、用全局/局部查询范围类实现跨模型复用;四、用过滤器服务解耦动态筛选;五、用访问器替代控制器中运行时计算。

如果您在Laravel框架中编写控制器时发现查询逻辑臃肿、重复或难以维护,则很可能是将本应封装在模型层的业务查询逻辑直接写入了控制器。以下是优化控制器查询逻辑的具体方法:
一、将查询逻辑移至模型作用域
模型作用域可将常用查询条件封装为可复用、可链式调用的方法,使控制器保持简洁并提升逻辑复用性。作用域定义后,控制器中仅需调用命名方法,无需重复编写where语句。
1、在Eloquent模型中定义本地作用域,方法名以scope开头,接收$query参数;
2、在作用域方法体内使用$query->where()等构建器方法添加约束;
3、在控制器中通过Model::scopeName()->get()方式调用,支持与其他作用域自由组合;
4、若需动态参数,可在作用域签名中追加参数,例如scopeOfType($query, $type);
5、调用时省略scope前缀,直接使用->ofType('admin')形式。
二、使用查询分组处理OR逻辑
当查询需表达“此条件或彼条件”且必须与其它AND条件共存时,必须用闭包包裹OR分支,否则因SQL运算符优先级导致逻辑错误,返回意外数据。
1、避免直接链式调用where()->orWhere();
2、改用where(function ($q) { ... })创建独立条件组;
3、在闭包内对同一组内的多个条件使用where()和orWhere();
4、该闭包整体作为单个AND单元参与外部查询;
5、例如双向消息查询必须写成where(...)->orWhere(...)嵌套于两个独立where()闭包中。
三、提取为模型查询范围类(Query Scopes)
对于跨多个模型或需复用的复杂查询逻辑,可定义独立的查询范围类,实现关注点分离与类型安全。此类不依赖模型实例,仅操作查询构建器,便于单元测试与复用。
1、创建新类实现Illuminate\Database\Eloquent\Scope接口;
2、在apply()方法中对$builder添加约束;
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
3、在模型的boot()方法中通过static::addGlobalScope(new XxxScope)注册;
4、如需局部启用,可配合withoutGlobalScope()临时禁用;
5、全局作用域适用于软删除、租户隔离等全模型统一规则。
四、引入自定义过滤器服务类
当控制器需响应前端传入的动态筛选参数(如搜索关键词、状态、时间范围),应将参数解析与条件注入解耦为独立过滤器服务,避免控制器充斥if-else分支。
1、新建服务类,接收Request对象和Builder实例;
2、在服务方法中根据请求参数存在性决定是否调用$builder->where();
3、支持whereBetween、whereIn、whereHas等多种条件构造;
4、控制器中仅需一行调用:app(SearchFilter::class)->apply($request, User::query());
5、该模式利于扩展多字段模糊搜索、关联字段过滤及权限字段自动追加。
五、使用Eloquent Accessor与Attribute替代运行时计算
对于非数据库字段但需频繁在查询结果中展示的派生值(如全名、状态标签、格式化日期),不应在控制器中循环赋值,而应利用访问器在模型层面统一处理。
1、在模型中定义getFullNameAttribute()方法;
2、该方法自动被$user->full_name触发,无需手动调用;
3、若需包含在toArray()或API响应中,需在$appends数组声明;
4、对于数据库中不存在但需参与查询的虚拟字段,可结合selectRaw与访问器协同;
5、避免在控制器循环中使用map()或foreach逐条计算,防止N+1与性能损耗。










