unique验证器无法校验数组内对象字段唯一性,仅支持单字段数据库唯一校验;需用自定义规则结合array_column与array_unique(SORT_REGULAR)实现内存级去重,并注意空值、类型一致性和嵌套深度问题。

ThinkPHP 6 中用 unique 验证器校验数组内对象字段唯一性行不通
直接说结论:unique 验证器只支持单字段数据库唯一校验,对 ['users' => [['name' => 'a'], ['name' => 'a']]] 这类请求体中的“数组内字段重复”完全无感知。它不解析嵌套结构,也不做内存级去重判断。
常见错误现象是:提交两个相同 name 的用户对象,验证通过,入库后才发现业务逻辑崩了;或者手动写循环去重但漏掉空值、类型不一致(如 "1" 和 1)等边界情况。
- 它只查数据库,不查当前请求数据内部是否重复
- 不支持
scene或rule动态传入数组路径 - 即使配合
in或regex,也无法表达“这个字段在当前数组所有子项中不能重复”
用自定义验证规则 + array_column + array_unique 做深度去重
核心思路:把嵌套数组中目标字段抽出来,用 PHP 原生函数做值去重,再比对数量。注意必须用 SORT_REGULAR 模式,否则 null、0、false 会被误判相等。
使用场景:批量创建/更新资源时,确保请求体中多个对象的某个业务字段(如 code、email、sort)互不重复。
立即学习“PHP免费学习笔记(深入)”;
- 先在验证器里定义规则:
['items.*.code' => 'required|alpha_dash|max:32'] - 再加一个独立规则校验去重:
'items' => ['array', 'check_items_code_unique'] -
check_items_code_unique方法里取array_column($value, 'code'),再用array_unique($codes, SORT_REGULAR)对比长度 - 别忘了过滤掉空值或无效子项,避免
array_column返回false
protected function checkItemsCodeUnique($value, $rule, $data): bool
{
if (!is_array($value)) return false;
$codes = array_column($value, 'code');
$codes = array_filter($codes, fn($v) => $v !== null && $v !== '');
return count($codes) === count(array_unique($codes, SORT_REGULAR));
}
用 Scene + append 动态注入校验,避免硬编码字段名
如果同一数组要校验多个字段(比如既要 code 唯一,又要 email 唯一),硬写多个 check_* 方法会爆炸。ThinkPHP 的 Scene 支持运行时追加规则,更灵活。
参数差异在于:不把字段名写死在方法名里,而是从 $data 或上下文提取,比如从 URL 参数 unique_field=code 获取。
- 在控制器里调用验证器时指定
scene:$validator->scene('batch_create')->check($data) - 在验证器
scenes属性中定义:'batch_create' => ['items', 'items.*.code', 'items.*.email'] - 用
append方法动态加规则:$this->append(['items' => ['check_array_unique:code,email']]) - 对应的方法接收第二个参数:
checkArrayUnique($value, $rule, $data, $field),其中$field是字符串"code,email"
注意 array_unique 的类型陷阱和性能临界点
当数组超过 500 项,且字段是字符串(尤其含中文或长 token),array_unique 在 SORT_REGULAR 模式下会有明显延迟。这不是 ThinkPHP 的锅,是 PHP 底层实现决定的。
容易踩的坑是:把 array_unique($arr, SORT_STRING) 当成万能解,结果 1 和 "1" 被当成不同值放过,实际业务要求严格类型一致。
- 开发期用
var_dump(array_count_values($codes))快速定位重复值 - 线上环境若数组项常超 200,建议改用
foreach+isset($seen[$code])手动去重,可控且更快 - 永远校验
$value是否为数组,防止前端传个字符串进来导致array_column报 Warning - 如果字段本身允许为空,明确 decide 是“空值之间可重复”还是“空值算一个有效值”,并在去重前统一处理
最麻烦的其实是嵌套再嵌套,比如 items.*.specs.*.sku —— 这时候别硬套 array_column,老老实实递归提取,或者让前端改结构。工具再好,也救不了设计失焦的接口。


















