ThinkPHP 5.0 管道字段 LIKE 搜索:传单值静默丢条件或炸 PDO
在某个后台管理系统里做商品搜索,需要同时匹配名称和编码两个字段。写出来的代码看起来非常"合理":
$q->where('name|code', 'LIKE', '%' . $kw . '%')->select();本地跑了一把,按名称搜索没问题,以为上线了。结果用编码搜索时,结果一片空白——而数据库里明明有匹配的行。偶尔换个关键词还会直接 500,日志里 SQLSTATE[HY093] Parameter count does not match。
现象
实际表现分两种,取决于 TP 5.0 的小版本和查询复杂度:
- PDO 异常:
SQLSTATE[HY093] Invalid parameter number / Parameter count does not match - 静默丢条件:只按第一个字段
name过滤,code的 LIKE 条件完全消失,SQL log 里能看到残缺语句
打开 SQL 日志确认:
-- 出错时可能看到残缺绑定
SELECT * FROM goods WHERE (name LIKE :data LIKE AND code LIKE )
-- 或只有 name 条件,code 没展开
SELECT * FROM goods WHERE name LIKE '%手机%'根因
TP 5.0 的 where('a|b', op, value) 管道语法把 a|b 展开成两个独立的 OR 条件:
name LIKE ? OR code LIKE ?展开后需要两个独立的 PDO bind 值。TP 5.0 的 Builder::parseWhereItem 在处理 pipe 字段时,会按 explode('|', $field) 循环,对应下标去取 $val[0]、$val[1]……
传进来的是标量字符串,$val[0] 会取到字符串第一个字符,$val[1] 取第二个字符,完全不是预期的 LIKE 值。部分版本甚至直接 bind 失败抛 PDO 异常。
TP 5.1+ / Laravel 是另一套查询构造器,单值是否广播行为不同,迁移习惯带来的陷阱。
where('a|b', 'LIKE', '%x%') 中第三个参数是标量时,TP 5.0 不会把同一个值复制给两个字段,而是按下标取字符。
这个 bug 在名称字段有值的情况下看起来"正常工作",只有测试编码搜索时才暴露,极容易漏测。
正解
方式 1 — 传等长数组(最简单,推荐)
$kwLike = '%' . $kw . '%';
// 管道有几个字段,数组就传几个值
$q->where('name|code', 'LIKE', [$kwLike, $kwLike])->select();
// 三字段同理
$q->where('name|code|remark', 'LIKE', [$kwLike, $kwLike, $kwLike]);
// 或者用 array_fill 更简洁
$q->where('name|code|remark', 'LIKE', array_fill(0, 3, $kwLike));方式 2 — whereOr 链 + closure(语义更清晰)
$kwLike = '%' . $kw . '%';
$q->where(function ($q) use ($kwLike) {
$q->where('name', 'LIKE', $kwLike)
->whereOr('code', 'LIKE', $kwLike);
});外层必须裹 closure,否则 whereOr 会把 OR 提到顶层,破坏与其他 AND 条件的布尔逻辑。
传数组,每个字段对应一个值,数量严格一致。推荐 array_fill(0, count($fields), $kwLike) 动态生成,避免手动对齐出错。
用户输入的 $kw 若含 % 或 _,要先转义,否则 a% 能匹配所有以 a 开头的行:
$kw = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $kwRaw);
$kwLike = '%' . $kw . '%';排查方法
全项目扫描存量可疑调用(管道字段 + 非数组第三参数):
grep -rnE "where\('[^'|]+\|[^']+',\s*'(LIKE|=|<>|>=|<=)'," application/ --include="*.php"人眼看:第三参数是否为数组,数组元素数是否等于管道字段数。
一句话外卖
ThinkPHP 5.0 管道字段查询中,
where('a|b', 'LIKE', value)的第三参数必须是与字段数等长的数组,单值不广播,漏传会静默丢条件或炸 PDO。