ThinkPHP 验证器里 `unique` 规则那个隐蔽的"表名陷阱":我帮用户改了个昵称,结果全库用户名跟着崩了

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

上周有个老用户反馈,说改昵称时系统提示"用户名已被占用"。我第一反应是缓存没清,让他等十分钟再试。结果他截图过来——占用的那个用户名,是他自己三年前注册的小号。

查了半天,问题出在验证规则上。先给大家看看我当初写的"经典错误示范":

// 错误写法:看似没毛病,实则埋雷 $validate = Validate::rule([ 'nickname' => 'require|unique:user', // 注意这里 'username' => 'require|unique:user', ]);

这段代码跑了一年多,为啥突然出事?因为 `unique:user` 这个简写,ThinkPHP 默认去查的是 当前请求对应的数据表,而不是你字面写的 `user` 表。更坑的是,如果控制器里前面某处用 `model('User')` 查过数据,验证器可能直接复用了那个查询实例的表名设定——而我前阵子给 `User` 模型加了个全局的 `soft_delete` 查询范围,连带把表别名也改了。

最终结果是:`nickname` 的 `unique` 校验,实际查的是 `user` 表没错,但 `username` 那条因为前面代码的"污染",偷偷去校验了另一张关联表的同名字段。用户的小号在那张关联表里有记录,于是自己的用户名被自己的小号"占用"了。

正确的写法应该显式指定完整参数,别让框架"猜":

// 正确写法:把表名、字段、排除条件都写死,不留模糊空间 $validate = Validate::rule([ 'nickname' => 'require|unique:user,nickname,' . $userId, 'username' => 'require|unique:user,username,' . $userId . ',id', ]);

或者更保险一点,直接闭包自定义:

'username' => ['require', function ($value, $data) use ($userId) { $exists = Db::name('user') ->where('username', $value) ->where('id', '', $userId) ->find(); return $exists ? '用户名已被占用' : true; }]

这个坑最阴险的地方在于:它不会报错。验证规则语法完全合法,校验逻辑也"正常执行"了,只是查错了表。如果不是用户刚好有个同名小号,我可能到现在都没发现全库有批用户的用户名校验是飘在别处的。

现在我的规矩是:所有 `unique` 规则必须带全四个参数,闭包校验优先。框架给的"便利"有时候是糖衣,有时候是炮弹,这次我算尝出味了。

你们有没有被验证器的"智能推断"坑过?欢迎交流,让我知道我不是一个人。

评论0
最新打赏 共 1 次 · 10 金币
张先生
张先生 主理圣羽星庭
社区管理
+10金币
回复 · 0
还没有回复
微信客服 微信客服