返回博客

ThinkPHP 5.0 管道字段 LIKE 搜索:传单值静默丢条件或炸 PDO

在某个后台管理系统里做商品搜索,需要同时匹配名称和编码两个字段。写出来的代码看起来非常"合理":

$q->where('name|code', 'LIKE', '%' . $kw . '%')->select();

本地跑了一把,按名称搜索没问题,以为上线了。结果用编码搜索时,结果一片空白——而数据库里明明有匹配的行。偶尔换个关键词还会直接 500,日志里 SQLSTATE[HY093] Parameter count does not match

现象

实际表现分两种,取决于 TP 5.0 的小版本和查询复杂度:

  1. PDO 异常SQLSTATE[HY093] Invalid parameter number / Parameter count does not match
  2. 静默丢条件:只按第一个字段 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。