ThinkPHP 的 input() 不自动创建嵌套数组层级,缺失中间键时返回 null;需用 array_merge_recursive() 或自定义 inputNested() 手动补全结构,JSON 输入同样不补结构且解析失败静默返回空数组。

ThinkPHP 的 input() 不会自动创建嵌套数组层级
直接调用 input('user.profile.name') 时,如果原始 POST 数据里 user 或 user.profile 根本不存在,input() 返回 null,不会帮你 new 出中间对象或空数组。这不是 bug,是设计使然——ThinkPHP 默认不做“结构预填充”,只做“安全取值”。
常见错误现象:input('a.b.c') 返回 null,但你预期它至少返回一个空字符串或空数组;或者后续代码直接对返回值调用 ->xxx() 导致 Fatal error。
- 使用场景:表单提交含深层嵌套字段(如 JSON 表单、多维数组型 name 属性),又不想在控制器里一堆
isset()套娃判断 - 参数差异:
input('a.b.c', '', 'htmlspecialchars')的默认值只作用于最终键,不作用于中间缺失层 - 性能影响极小,手动补全一次即可,无运行时反射开销
用 input() + array_merge_recursive() 手动补全结构
最轻量、无依赖的解法:先定义好期望的结构模板,再和实际输入做递归合并。注意不是覆盖,而是“缺啥补啥”。
示例:你希望总有 user → profile → name 和 avatar,哪怕前端没传:
立即学习“PHP免费学习笔记(深入)”;
$default = [
'user' => [
'profile' => [
'name' => '',
'avatar' => ''
]
]
];
$data = array_merge_recursive($default, input());
// 此时 $data['user']['profile']['name'] 必然存在
- 不要用
array_replace_recursive():它会把字符串值也当数组展开,导致意外结果 - 避免在全局中间件里无差别 merge,默认结构要按接口收敛,不同方法用不同模板
- 如果原始输入含数字索引数组(如
tags[0]),array_merge_recursive()会重排键名,此时应改用自定义递归赋值函数
自定义 inputNested() 辅助函数处理深层缺失
把补全逻辑封装成函数,比每次写 array_merge_recursive() 更可控,也能兼容点号路径语法。
实操建议:在 app/common.php 里加一个辅助函数:
function inputNested($key, $default = null, $filter = '') {
$keys = explode('.', $key);
$data = input('', [], $filter); // 取全部原始输入
$ptr = &$data;
foreach ($keys as $k) {
if (!isset($ptr[$k])) {
$ptr[$k] = is_array($default) ? [] : $default;
}
$ptr = &$ptr[$k];
}
return $ptr;
}
- 调用
inputNested('user.profile.name', ''),即使user为空,也会返回空字符串;若user存在但缺profile,则自动创建profile数组 - 该函数不修改原始
input()结果,只做路径穿透+懒创建,适合高频嵌套取值场景 - 注意:不能用于取数组列表(如
inputNested('items.0.name')),因为0是数字键,需额外判断是否为整型并初始化为数组
JSON 请求体下 input() 的行为差异
当 Content-Type 是 application/json 时,ThinkPHP 会自动解析 JSON 到 input(),但依然不补结构。更关键的是:JSON 解析失败时,input() 返回空数组,而不是抛异常——这会让“结构缺失”更难被发现。
- 验证方式:在控制器开头加
if (empty(input()) && !request()->isJson()) { throw new \Exception('Invalid JSON'); } - 如果你依赖 JSON 深层字段,建议在
app/middleware/CheckJsonInput.php中统一校验并预设结构模板 - 别依赖
input('$.user.profile.name')这类 JS 风格路径:ThinkPHP 不支持,会直接返回null
null 表示“用户确实没填”?这个边界必须由业务决定,框架不替你选。


















