应优先使用 rule::unique() 而非字符串写法,更新时必加 ignore() 排除自身,并通过 where() 过滤软删除或自定义状态,跨表验证可叠加多个 rule::unique(),同时数据库必须建立对应唯一索引以防并发冲突。

直接用 Rule::unique() 而不是字符串写法,能避开 80% 的更新失败、主键误判和软删除漏过滤问题。
更新时必加 ignore(),否则自己改自己会报“已存在”
表单编辑提交时,哪怕邮箱没变,unique:users,email 也会查出当前记录,当成重复值拒绝保存。必须显式排除自身:
-
Rule::unique('users', 'email')->ignore($user)—— 最安全:自动识别主键名(id 或 uuid 都行) -
Rule::unique('users', 'email')->ignore($user->id, 'id')—— 显式指定字段,适合主键非 id 的场景 - 别用字符串写法
'unique:users,email,'.$user->id:一旦$user未定义或为空,直接抛Undefined variable - 如果 ID 来自 request(如
request('id')),务必先校验存在性,否则 ignore(null) 会失效,变成查所有记录
要跳过软删除或状态为“已删除”的记录,用 where() 链式过滤
Rule::unique() 默认只忽略 deleted_at IS NULL 的软删除逻辑,但业务中常有自定义“删除态”(比如 status = 4),需手动排除:
Rule::unique('users','email')->ignore($user)->where(fn($q) => $q->whereNotIn('status', [4]))- 多个条件不能塞进一个
where(['status' => 4, 'type' => 'temp'])—— 这会生成WHERE status IN (4) AND type IN ('temp'),语义错误 - 若条件值可能为空(如
$companyId为 null),先判断再链式调用,否则生成WHERE company_id = NULL(不是IS NULL),查不到数据
跨表/多字段唯一性验证,别硬拼字符串,用多个 Rule::unique()
一个手机号既要不在 users.mobile 出现,也不能在 members.phone 重复,直接叠加规则即可:
['required', Rule::unique('users', 'mobile'), Rule::unique('members', 'phone')]- 每个
Rule::unique()可独立配ignore()和where(),互不影响 - 字符串写法用竖线连接(
'unique:users,mobile|unique:members,phone')也能工作,但无法对每个规则单独加条件,灵活性差 - 注意:数据库层面仍需建对应唯一索引,否则并发插入时验证会失效 ——
Rule::unique()只是应用层防护
中文提示不生效?错误消息 key 必须匹配规则结构
默认的 The :attribute has already been taken. 对用户不友好,但改错 key 就白配:
- 字符串写法
'unique:users,email'对应的 message key 是unique(全局)或email.unique(字段级) -
Rule::unique('users','email')同样走unique规则,**不区分构造方式**,所以不用写成email.Rule::unique - 最简覆盖:在
resources/lang/zh/validation.php里加'unique' => ':attribute 已被占用,请换一个' - 如果加了
where()或ignore(),依然属于unique规则范畴,无需额外 key
真正容易被忽略的是:数据库索引和应用验证必须同时存在。哪怕 Rule::unique() 写得再严谨,没在迁移里加 $table->unique(['email', 'deleted_at']),高并发下照样能插出两条相同邮箱的记录 —— 验证器拦不住 race condition。











