laravel 的 rule::unique() 不支持直接逗号分隔联合字段,须用 where() 添加条件;编辑时需 ignore() 排除自身 id;数据库必须建联合唯一索引以防止并发冲突。

unique 规则不支持联合字段?用 Rule::unique() 配合 where()
直接在 unique 规则里写两个字段(比如 "email,company_id")会报错或失效——Laravel 的原生 unique 验证器只认单字段名,逗号分隔的写法是旧版遗留语法,5.8+ 已弃用且不生效。
正确做法是用 Rule::unique() 构造器,配合 where() 添加额外条件:
use Illuminate\Validation\Rule;
Validator::make($data, [
'email' => [
'required',
Rule::unique('users')->where(fn ($query) => $query->where('company_id', $data['company_id'])),
],
]);
- 必须用
Rule::unique('表名'),不能只写'unique:users',否则where()无法链式调用 -
$data['company_id']要确保已存在且非空,否则where()会生成WHERE company_id = NULL,查不到数据导致误通过 - 如果
company_id来自请求以外(比如当前用户所属公司),应从上下文取值,而非依赖$data
更新时排除自身 ID:忽略当前模型记录
编辑已有记录时,如果不排除自己,验证会因“当前邮箱已被自己占用”而失败。这是最常被跳过的一步。
Rule::unique() 提供 ignore() 方法,但要注意参数顺序和类型:
Rule::unique('users')
->where(fn ($query) => $query->where('company_id', $company_id))
->ignore($user->id)
-
ignore()第一个参数是主键值(如123),不是模型实例;传$user会触发隐式转换,可能出错 - 如果主键不是
id(比如叫uuid),必须显式指定字段名:->ignore($user->uuid, 'uuid') - 在表单提交中,
$user->id必须来自数据库真实记录,不能是前端传来的伪造 ID
数据库层面加联合唯一索引,不是可选项
只靠 PHP 层验证,高并发下仍可能插入重复数据。比如两个请求几乎同时通过验证,再先后执行 INSERT,就会突破唯一性。
必须在迁移中添加数据库约束:
Schema::table('users', function (Blueprint $table) {
$table->unique(['email', 'company_id']);
});
- 索引名建议显式指定,避免 Laravel 自动生成难以识别的长名:
->unique(['email', 'company_id'], 'users_email_company_unique') - 如果表已有数据,加索引前先清理重复项,否则迁移会失败
- MySQL 中该索引允许多行
NULL值(即company_id为 NULL 时不受限),若业务不允许,需额外用 CHECK 约束或应用层拦截
FormRequest 中复用联合唯一规则
把逻辑写死在控制器里不利于维护。推荐抽到 FormRequest 的 rules() 方法中,但要注意闭包捕获变量的时机。
错误写法:where('company_id', $this->company_id) —— 如果 company_id 是可选字段或未提交,$this->company_id 可能为空或默认值。
- 应在
rules()内部动态取值,比如:$this->input('company_id')或$this->route('company') - 若依赖关联模型(如当前用户所属公司),建议在
authorize()或构造函数中提前加载并缓存,避免多次查询 - 测试时容易漏掉「company_id 缺失」场景,此时应明确拒绝(422)还是降级为单字段校验,需按业务定
ignore() 或 where() 条件,导致编辑失败或漏校验。











