ThinkPHP6 查询构造器里那个 `whereOr` 陷阱:我因为括号位置不对,把全站用户数据暴露给了前台

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 63 浏览 0 回复

上周帮朋友看一个后台筛选接口的bug,排查过程让我血压飙升——问题极其隐蔽,后果却极其离谱。写出来给大家提个醒,尤其是习惯"链式操作写到哪算哪"的同学。

先上错误代码,看看能不能一眼发现问题:

```php
// 需求:查询"状态正常 且 (用户名包含关键词 或 手机号包含关键词)"的用户
$list = User::where('status', 1)
    ->where('username', 'like', "%{$keyword}%")
    ->whereOr('phone', 'like', "%{$keyword}%")
    ->select();
```

这段代码跑起来"似乎正常",测试账号搜出来也对。但仔细看生成的SQL:

```sql
SELECT * FROM `user` WHERE `status` = 1 AND `username` LIKE '%xxx%' OR `phone` LIKE '%xxx%'
```

因为AND优先级高于OR,实际执行的是(status=1 AND username LIKE) OR (phone LIKE)——手机号匹配上的用户,不管status是不是1,全出来了!

更坑的是,如果测试数据里status全是1,你根本发现不了。我朋友就是上线后被人刷接口,才发现能搜到已封禁账号的敏感信息。

正确写法:用闭包显式包裹OR条件

```php
$list = User::where('status', 1)
    ->where(function ($query) use ($keyword) {
        $query->where('username', 'like', "%{$keyword}%")
              ->whereOr('phone', 'like', "%{$keyword}%");
    })
    ->select();
```

生成的SQL变成:

```sql
SELECT * FROM `user` WHERE `status` = 1 AND (`username` LIKE '%xxx%' OR `phone` LIKE '%xxx%')
```

括号一加上,逻辑就对了。

还有个更隐蔽的变体坑

如果你习惯把条件拆成数组传参,也得小心:

```php
// 错误:whereOr在数组里不会自动加括号
User::where([
    ['status', '=', 1],
    ['username', 'like', "%{$keyword}%"],
    ['phone', 'like', "%{$keyword}%", 'or'] // 这里的'or'只是当前条件的连接符
])->select();
```

这种写法生成的SQL和第一个错误案例一模一样,OR只作用于phone这一个条件,不会把username包进去。

我现在养成的习惯:

1. 只要出现whereOr,先问自己"前面的条件要不要被包进去"
2. 复杂查询必开DB调试看最终SQL,Db::listen或者断点都行,别信直觉
3. 团队代码审查时,看到whereOr自动提高警惕,比看到eval还紧张

这bug最讽刺的是,ThinkPHP文档其实写得清清楚楚,但链式调用的"流畅感"太容易让人放松警惕。写出来算给自己再敲一次警钟,也希望大家别在同样的阴沟里翻船。

你们有没有被查询构造器的优先级坑过?欢迎补充其他框架的类似陷阱。

评论0
回复 · 0
还没有回复
微信客服 微信客服