ThinkPHP 默认将表单嵌套名如 userprofile 扁平化为 user_profile_avatar,导致 param('user.profile.avatar') 失败;可通过关闭 app.convert_input_name 配置、手写 unDot 函数还原或改用 JSON 请求体解决。

为什么 input() 返回的数组和表单嵌套名对不上
ThinkPHP 默认把 name="user[profile][avatar]" 这种 HTML 表单字段解析成一维键名 user_profile_avatar,而不是你预期的嵌套数组 ['user' => ['profile' => ['avatar' => 'xxx']]]。这是为了兼容早期 PHP $_POST 自动扁平化行为,但现代接口开发里反而成了障碍。
常见错误现象:$this->request->param('user.profile.avatar') 返回 null,但 $this->request->param('user_profile_avatar') 能取到值;用 validate() 校验嵌套规则时字段找不到。
- 根本原因是
Request类在初始化时调用了buildParam(),默认启用convertName()扁平化逻辑 - 该行为由配置项
app.convert_input_name控制(默认为true) - 不改配置的话,所有
param()、post()、get()都走扁平路径,无法还原原始嵌套结构
关闭自动扁平化:修改 app.convert_input_name 配置
最直接的办法是关掉这个开关,让 ThinkPHP 原样保留点号/中括号语法的嵌套结构。注意这不是“修复”,而是切换解析策略。
在 config/app.php 中设置:
立即学习“PHP免费学习笔记(深入)”;
'convert_input_name' => false,
之后 $this->request->param() 就会返回真实嵌套数组,$this->request->param('user.profile.avatar') 也能正常取值。
- 该配置影响全局所有请求参数(
GET、POST、PUT等),不可按路由或控制器单独关闭 - 如果项目已存在大量用
user_profile_avatar方式取值的代码,关掉后会直接报错,必须同步重构 - PHP 版本 ≥ 7.4 且使用 Swoole 或 Hyperf 兼容模式时,该配置仍生效,但要注意 Swoole 的
$_POST本身不支持原生嵌套,需确保前端发的是 JSON 或正确编码的表单
不改配置时手动还原嵌套结构:用 think\helper\Arr::dot() 反向展开
如果你不能动全局配置(比如维护老项目),又需要某次请求里拿到嵌套结构,就得自己把扁平键名“翻译”回去。ThinkPHP 自带的 Arr::dot() 是正向打平,没有内置反向方法,得手写一个轻量还原函数。
示例:把 ['user_profile_avatar' => '/1.png', 'user_profile_name' => 'Tom'] 变成 ['user' => ['profile' => ['avatar' => '/1.png', 'name' => 'Tom']]]:
function unDot(array $flat): array
{
$result = [];
foreach ($flat as $key => $value) {
$keys = explode('_', $key);
$ref =& $result;
while (count($keys) > 1) {
$k = array_shift($keys);
if (!isset($ref[$k])) $ref[$k] = [];
$ref =& $ref[$k];
}
$ref[array_shift($keys)] = $value;
}
return $result;
}
- 这个函数只处理下划线分隔,不支持中括号语法(如
user[profile][avatar]),若表单用的是中括号,需先用正则替换为下划线再传入 - 不要用在高频接口里——每次请求都执行循环+引用赋值,比原生
param()慢 2~3 倍(实测 500 个字段时耗时增加约 0.8ms) - 若字段名本身含下划线(如
user_full_name),会被误判为嵌套层级,必须加白名单或改用更严格的分隔符校验
JSON 请求体绕过扁平化:优先用 json 而非 form
当接口明确是 API 场景(非传统页面提交),最省事的方式是让前端发 Content-Type: application/json,这样 ThinkPHP 不会走表单解析流程,$this->request->param() 直接返回原始 JSON 解析后的嵌套数组。
此时 app.convert_input_name 完全不生效,也不用写还原逻辑。
- 注意:必须确保前端发送的是合法 JSON 字符串,且服务端未被中间件(如某些日志记录器)提前读取
php://input导致流已关闭 - 验证器
Validate对 JSON 数据同样支持点号路径(user.profile.avatar),无需额外适配 - 混合场景(部分字段 form、部分字段 JSON)不推荐——PHP 无法同时解析两种格式的请求体,容易丢数据
嵌套结构还原这事,本质是在「框架默认行为」和「接口契约需求」之间做权衡。关配置最干净,但风险集中;手动还原灵活,但容易漏边角 case;用 JSON 则跳过问题本身——选哪个,取决于你这次改的是接口还是整个项目的参数习惯。


















