直接拼接sql字符串在搜索场景下极易失控,因每新增过滤字段(如status、created_after)均需增加if分支、空值判断及注入风险点,导致where子句混杂and/or/in/like并动态join,实为维护脆弱的字符串状态机;而querybuilder通过结构化条件入栈、延迟编译、绑定参数及白名单校验,实现安全与可维护性统一。

为什么直接拼接SQL字符串在搜索场景下很快失控
因为每个新增的过滤字段(比如 status、created_after、category_id)都意味着更多 if 分支、更多空值判断、更多 SQL 注入风险点。你很快会发现:同一个 WHERE 子句里混着 AND、OR、IN、LIKE,还可能要动态加 JOIN —— 这不是写逻辑,是在维护一个脆弱的字符串状态机。
用 QueryBuilder 类封装 WHERE 条件链式调用
核心是把「条件是否生效」和「SQL 片段生成」解耦。不靠字符串拼接,而是用内部数组累积条件,最后统一编译。例如:
$query = (new QueryBuilder('products'))
->where('status', '=', 'active')
->where('price', '>=', 100)
->whereIn('category_id', [1, 5, 8])
->whereLike('name', '%phone%');
关键点:
-
where()内部只做参数校验和条件入栈,不生成 SQL; - 每个条件存为结构化数组:
['field' => 'status', 'operator' => '=', 'value' => 'active']; - 避免在构造函数里预设固定字段,允许任意字段名(但需白名单校验);
- 所有用户输入必须经
bindValue()绑定,绝不用字符串插值。
Builder 模式里最容易被忽略的边界:NULL 和空数组处理
前端传 { "category_id": null } 或 { "tags": [] } 是常见情况,但直接丢进 WHERE category_id = ? 会导致查不到数据,而 IN () 会报语法错误。必须显式拦截:
-
where()遇到null值默认跳过该条件(不加IS NULL,除非显式调用whereNull('field')); -
whereIn()对空数组返回空条件(不生成IN ()),而不是抛异常; - 提供
whereOptional()方法,仅当值存在且非空时才加入条件; - 字段白名单检查必须在
where()入口就做,防止攻击者传admin=1绕过权限。
执行前必须调用 build() 才真正生成 SQL
Builder 对象本身不持有 PDO 实例,也不执行查询 —— 它只负责输出安全的 SQL 字符串和绑定参数数组。这样能复用同一 Query 对象做 COUNT(*) 和分页查询:
$query = (new QueryBuilder('orders'))
->where('user_id', $uid)
->where('status', 'shipped');
$countSql = $query->select('COUNT(*)')->build();
$itemsSql = $query->select('*')->limit(20)->offset(0)->build();
// 两者共用同一组绑定参数
$stmt = $pdo->prepare($countSql);
$stmt->execute($query->getParams());
注意:build() 是唯一触发 SQL 编译的入口,之前所有方法都只是设置状态。漏掉这步,你会拿到一个没生成 SQL 的对象,而不是报错 —— 这个静默失败最容易在测试中被忽略。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











