allowField() 是唯一能过滤非法字段的机制,$strict=true 仅校验字段存在性;需确保字段名与数据库列名完全一致、避免静态调用、动态白名单须清洗校验并绑定同一实例,且不作用于查询。
allowField(),不是 $strict = true
很多人误以为设 $strict = true 就能拦住非法字段,其实它只在字段不存在时抛异常,对合法但不该提交的字段(比如 password、is_admin)完全没作用。真正起过滤作用的是 allowField(),它会在写入前把数据裁剪成指定字段列表。
常见错误现象:
$model->allowField(['id', 'name'])->save($data),但实际入库了 token 字段 → 检查是否用了静态方法 User::save($data),它不走实例上的 allowField
user_name,白名单却传了 userName → ThinkPHP 不做自动转换,字段名必须和数据库列名完全一致(含下划线、大小写)Db::name('user')->update($data) → 绕过模型,allowField 失效,只能手动 array_intersect_key($data, array_flip($whitelist))
从数据库或配置读出来的字段数组不能直接喂给 allowField():空值、重复项、非字符串、非法字符都会导致静默失效,且不报错。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
array_filter($dbFields, 'is_string') 去掉 null、false、数字等非字符串项array_unique(),避免同字段出现多次(new User())->getSchema()->getFields() 获取真实字段列表,确保每个白名单字段都存在$user = new User(); $user->allowField($fields)->save($data)
每次请求都查库加载白名单,高并发下容易拖慢接口;硬编码在控制器里又难维护。推荐封装进模型,复用逻辑且边界清晰。
示例(UserModel.php):
public function saveWithScene($data, string $scene){ $fields = Db::name('field_whitelist') ->where('scene', $scene) ->where('status', 1) ->column('field_name'); $fields = array_unique(array_filter($fields, 'is_string')); $validFields = array_intersect($fields, $this->getSchema()->getFields()); return $this->allowField($validFields)->save($data);}
控制器中调用:$user->saveWithScene($data, 'register')。这样既隔离了白名单加载逻辑,又保证了字段校验和实例绑定一步到位。
allowField() 只管写入,不管查询allowField() 对 select()、find() 完全无效。想防止 SELECT * 泄露敏感字段,必须显式指定字段,比如 User::field(['id', 'name', 'email'])->select()。
容易被忽略的点:
allowField 不会传递给关联模型,每个关联模型需单独设置with() 预载时,关联模型字段仍可能被全量查出,除非你在关联定义里也加 field()
delete_time)若不在白名单里,allowField 不会自动排除它——它只过滤你传进去的 $data,不干预模型默认行为