直接写 where() 会覆盖之前的所有条件,而 andwhere() 是追加 and 条件;首次用 where(),后续须用 andwhere() 或 orwhere(),否则仅最后一条 where 生效。

QueryBuilder 的 where 和 andWhere 有什么区别
直接写 where() 会覆盖之前的所有条件,而 andWhere() 是追加 AND 条件。新手常误以为多次调用 where() 就是“叠加”,结果只留下最后一次的条件。
常见错误现象:$qb->where('u.status = :status')->where('u.createdAt > :date') —— 实际只生效第二个条件。
- 正确做法:首次用
where(),后续全部用andWhere()或orWhere() - 参数必须用命名占位符(
:name),不能拼字符串,否则 SQL 注入且类型绑定失效 - 如果条件逻辑复杂(比如带括号的嵌套),优先用
expr()构建表达式,而不是硬写字符串
怎么安全地处理空值或动态条件
真实业务里,查询参数经常是可选的(比如搜索表单里“状态”字段没填)。直接拼条件容易出错,也破坏 QueryBuilder 的链式结构。
推荐做法是先构建基础 QueryBuilder,再按需追加条件:
$qb = $em->createQueryBuilder()->select('u')->from(User::class, 'u');
if ($status !== null) {
$qb->andWhere('u.status = :status')->setParameter('status', $status);
}
if ($minDate) {
$qb->andWhere('u.createdAt >= :minDate')->setParameter('minDate', $minDate);
}
- 每个
setParameter()必须匹配前面占位符名,且最好在追加条件后立刻设置,避免遗漏或错位 - 不要在循环里反复调用
setParameter()同一个键名——会覆盖,不是累加 - 对数组类型的参数(如
IN查询),用$qb->expr()->in('u.id', ':ids')+setParameter('ids', $idArray),Doctrine 会自动展开
IN 查询和 NULL 判断怎么写才不出错
IN 和 IS NULL 是高频但易错的场景。直接写 'u.role IN :roles' 会报错,因为 Doctrine 不支持对命名参数直接展开数组;写 'u.updatedAt IS NULL' 看似简单,但若字段允许 NULL 且你用了 = NULL,就永远查不到数据(SQL 里 NULL 比较必须用 IS)。
-
IN正确写法:$qb->andWhere($qb->expr()->in('u.role', ':roles'))->setParameter('roles', ['admin', 'editor']) -
IS NULL必须用表达式:$qb->andWhere($qb->expr()->isNull('u.archivedAt')),不能写字符串'u.archivedAt IS NULL' - 混合使用时注意括号优先级,比如
AND (a = ? OR b IS NULL),要用$qb->expr()->orX()显式包裹
为什么用 expr() 而不是拼字符串
看起来 $qb->where("u.name LIKE '%{$keyword}%'") 更快,但它绕过参数绑定、无法转义、不兼容不同数据库的函数语法(比如 MySQL 的 DATE() vs PostgreSQL 的 DATE_TRUNC()),而且一旦 $keyword 含单引号就直接 SQL 报错。
-
$qb->expr()->like('u.name', $qb->expr()->literal('%'.$keyword.'%'))是错的——literal()不防注入,仍该用参数化:$qb->expr()->like('u.name', ':kw')+setParameter('kw', '%'.$keyword.'%') - 日期截断这类操作,用
$qb->expr()->gte('u.createdAt', $qb->expr()->dateSub('CURRENT_DATE()', '7', 'DAY')),比手写DATE_SUB(NOW(), INTERVAL 7 DAY)更可移植 - 性能上,表达式本身不慢,慢的是滥用
func()或嵌套太深导致 MySQL 无法走索引——这点得结合 EXPLAIN 看,不是 QueryBuilder 层能解决的
最常被忽略的一点:QueryBuilders 是可复用对象,但 setParameter() 是累积的。如果你把同一个 $qb 实例反复用于不同请求,没清空参数,就会混入旧值。要么每次新建,要么用 $qb->resetDQLPart('parameters')。











