控制器里写复杂查询易致测试困难、复用性归零、调试成本高;仅允许简单一次性读操作,whereraw易引发硬编码、参数错位、方言不兼容及sql注入风险。

能,但不建议在控制器里直接写复杂查询逻辑。
为什么控制器里写查询容易出问题
控制器的核心职责是接收请求、调用业务逻辑、返回响应。一旦把 whereRaw、多表 join、嵌套子查询或带参数绑定的原生 SQL 直接塞进控制器方法里,会立刻带来三个现实问题:
- 测试困难:无法单独对查询逻辑做单元测试,只能走 HTTP 集成测试
- 复用性归零:同样查“本月活跃用户”,在订单页、后台统计页、导出接口里各写一遍,参数处理不一致就埋坑
- 调试成本高:出错时分不清是路由传参没校验、还是
whereRaw里日期函数写错、或是 PDO 绑定顺序乱了
哪些查询可以留在控制器里
仅限简单、无业务含义、一次性的读操作。比如:
DB::table('settings')->where('key', 'site_name')->value('value')-
User::findOrFail($id)(Eloquent 查单条主键) DB::table('logs')->latest()->limit(10)->get()
这些操作不涉及计算、不依赖其他模型状态、不被多个地方复用——留着图个快,但得有心理准备:下次加个条件就得挪走。
whereRaw 出现在控制器里时最常踩的坑
whereRaw 是控制器里最容易失控的点,常见错误包括:
- 写死 SQL 片段:
->whereRaw("created_at >= '2025-01-01'")—— 时间硬编码,部署到新环境就失效 - 参数顺序错位:
->whereRaw('status = ? AND votes > ?', [$votes, $status])—— 占位符和数组顺序不匹配,查出空结果还报不出错 - 忽略数据库方言:
->whereRaw('DATE(created_at) = CURDATE()')在 SQLite 下直接报错,而 MySQL 能跑 - 拼接用户输入:
->whereRaw("name LIKE '%{$request->q}%'")—— 彻底绕过参数绑定,SQL 注入高危
更合理的分层路径
把查询逻辑从控制器里抽出来,不是为了“架构漂亮”,而是为了应对真实迭代压力:
- 简单复用 → 提取为 Eloquent 模型的
scopeActive或scopeByMonth方法 - 跨模型组合 → 写成 Repository 类,例如
UserReportRepository,统一管理join、selectRaw、分页偏移 - 报表类重查询 → 单独建
App\Queries\MonthlyUserGrowthQuery,专注封装 SQL + 参数校验 + 缓存键生成
真正麻烦的从来不是“能不能写”,而是“改三次之后谁还记得当初 whereRaw 里那个 COALESCE 是为了解决哪个 NULL 场景”。











