thinkphp 6 多字段模糊搜索需用闭包统一 whereor,否则逻辑错误;须过滤输入防止 sql 注入;空格/大小写问题需标准化处理;分页时搜索条件须复用闭包确保 count 与 select 一致。

ThinkPHP 6 的 whereLike 和 whereOr 组合容易漏掉字段
后台快捷搜索通常要对多个字段做模糊匹配(比如同时查 title、content、author),但直接写 whereLike('title', '%关键词%') 再加 whereOr 很容易只生效最后一个条件,或因括号层级错乱导致 SQL 逻辑错误。
正确做法是用闭包构造统一的 OR 条件块:
$keywords = $this->request->param('q', '');
Db::name('article')
->where(function ($query) use ($keywords) {
$query->where('title', 'like', "%{$keywords}%")
->whereOr('content', 'like', "%{$keywords}%")
->whereOr('author', 'like', "%{$keywords}%");
})
->select();
- 必须把所有
whereOr放在同一个闭包里,否则 ThinkPHP 会把它们平铺到主查询,变成 AND 关系 - 注意
whereOr是“追加 OR”,不是“独立 OR”,所以第一个条件得用where起头 - 如果字段类型是 JSON 或含特殊字符(如英文逗号分隔的标签),不能直接
like,得先json_extract或用find_in_set替代
全局搜索时 input 参数未过滤引发 SQL 注入风险
很多人直接把 $this->request->param('q') 拼进 like 条件,但 ThinkPHP 不会自动转义通配符 % 和 _,攻击者传 q=a%b 就可能绕过预期匹配范围,甚至结合 escape 字符触发注入。
安全写法是显式调用 escape 并限定搜索长度:
$q = trim($this->request->param('q', ''));
if (strlen($q) > 32) {
$q = substr($q, 0, 32);
}
$q = str_replace(['%', '_', '\'], '', $q); // 剥离通配符(或改用 addcslashes 配 escape)
$list = Db::name('user')->where(function ($query) use ($q) {
$query->where('username', 'like', "%{$q}%")
->whereOr('email', 'like', "%{$q}%");
})->select();
- 不要依赖
think-swoole或中间件自动过滤,它不处理like场景下的通配符 - 若业务允许前缀匹配(如搜索“张”只查姓氏),优先用
where('name', 'like', "{$q}%"),避免两端通配,性能更好 - MySQL 8.0+ 可考虑用
REGEXP配合全文索引,但需确认表引擎和字段是否支持
搜索字段含空格或大小写敏感时结果异常
用户搜“think php”,数据库里存的是“ThinkPHP”,或字段值前后有空格,like "%think php%" 就完全不命中。这不是 ThinkPHP 的 bug,而是底层 SQL 行为。
临时解法是在查询前标准化输入,并在字段上做函数处理:
$q = strtolower(str_replace(' ', '', $this->request->param('q', '')));
Db::name('doc')->where(function ($query) use ($q) {
$query->whereRaw("LOWER(REPLACE(title, ' ', '')) like '%{$q}%'")
->whereOrRaw("LOWER(REPLACE(content, ' ', '')) like '%{$q}%'");
});
-
whereRaw绕过了 ThinkPHP 的参数绑定,必须手动确保$q已清理——这也是为什么上面先做了strtolower和str_replace - 这种写法无法走索引,数据量大时建议建生成列(generated column)+ 索引,例如 MySQL 中:
ALTER TABLE doc ADD COLUMN title_clean VARCHAR(255) STORED AS (LOWER(REPLACE(title, ' ', ''))) - 如果用 PostgreSQL,直接上
ILIKE更省事,但 ThinkPHP 默认不识别,得用whereRaw显式写
分页 + 快捷搜索下 count() 和 select() 查询不一致
常见现象:列表显示 12 条,但分页栏显示总共 200 页;或者第一页正常,翻到第二页就空了。根本原因是 paginate() 内部执行了两次查询,而你的搜索逻辑里用了动态变量、时间戳或随机排序,导致 count 和 select 的 WHERE 条件实际不同。
最稳妥的方式是把搜索条件抽成可复用的闭包:
$search = function ($query) use ($q) {
$query->where('status', 1)
->where(function ($q2) use ($q) {
$q2->where('title', 'like', "%{$q}%")
->whereOr('desc', 'like', "%{$q}%");
});
};
$list = Db::name('product')
->where($search)
->order('create_time desc')
->paginate(15);
// paginate 会自动复用 $search 闭包,保证 count 和 select 一致
- 别在闭包里引用
$this或$request,因为paginate执行count时可能已脱离请求上下文 - 如果用了软删除(
useSoftDelete),确保where($search)在withTrashed()或onlyTrashed()之后调用,顺序错会导致 deleted_at 过滤失效 - 调试时可用
Db::getLastSql()分别打印 count 和 select 的 SQL,对比 WHERE 部分是否真的一致
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











