
ThinkPHP中input()返回的数组为什么改着改着就变了
因为input()默认返回的是原始请求数据的引用(尤其在PHP 7.4+及ThinkPHP 6.x中,当底层用&$_POST或&$_GET做映射时),不是深拷贝。你对$data[‘user’][‘profile’][‘age’]赋值,可能同步改掉后续其他逻辑里依赖的同一份$data。
典型现象:input(‘user/a’)取值后调用unset()或array_merge_recursive(),再传给验证器或模型,结果验证失败——不是规则问题,是数据结构已被前面某处“动过”ThinkPHP 6.0+ 的input()默认行为不自动克隆,它只做键名映射和类型转换,不做内存隔离不要依赖input()返回值的“不可变性”,它本质上就是个便捷访问器,不是数据快照
用input(”, ”, false)禁用自动过滤但不解决克隆问题
第三个参数设为false只是跳过filter回调和htmlspecialchars等处理,**不会触发深拷贝**。很多人误以为“关了过滤就安全了”,其实只是绕过了转义,原始数组引用还在。
input(‘user’, [], false) → 返回的是$_POST[‘user’]的直接引用(若存在)想真正隔离,必须显式克隆:用unserialize(serialize($data))或json_decode(json_encode($data), true)注意性能:大嵌套数组用serialize比json快,但含资源、闭包、对象会失败;纯数组推荐json方案
ThinkPHP 6.1+ 推荐用Request::param()配合Arr::deepClone()
TP6.1 引入了think\helper\Arr工具类,其中deepClone()是专为这种场景设计的轻量深克隆函数,比手写递归或序列化更可靠,也兼容stdClass和空值。
正确姿势:$raw = request()->param(‘user’);$cloned = \think\helper\Arr::deepClone($raw);它能处理null、int、string、array、stdClass,但不处理自定义对象(这点和serialize一致)别混用:input()和request()->param()底层来源不同(前者走Input类缓存,后者走Request实例),在中间件或钩子中多次调用input()可能拿到被污染的副本
验证前必须克隆?不一定,但关键节点要守住
不是所有地方都要克隆。重点守住三个位置:验证器初始化、模型save()前的数据组装、跨方法传递的请求数据副本。
立即学习“PHP免费学习笔记(深入)”;
验证器本身不修改输入数据,但如果你在scene里调用only()或remove(),它操作的是传入的引用 —— 所以validate($data)前务必先克隆模型create()或save()接受数组时,内部会做array_merge等操作,若该数组来自input()且之前被改过,结果不可控最易忽略的点:日志记录或审计中间件里调用了input(),又没做克隆,后续业务逻辑里的input()就可能已变形复杂的地方在于,TP没有全局开关控制“所有input()都返回克隆副本”。你得自己判断哪些数据流会跨作用域、会被多个模块读写 —— 尤其是带嵌套结构的JSON请求体,一不留神就共享引用。

评论(0)