ThinkPHP .env Section 前缀陷阱:config() 静默返回默认值的真相
某天,某后台管理系统的内部 API 突然全量返回 403。日志显示认证失败,但运维反复确认:.env 里的密钥值和调用方配置一字不差。排查两小时,最后发现问题根本不在密钥值本身,而在 .env 文件里一个毫不起眼的 section 分组——密钥写在了错误的 [SECTION] 下方。
现象
config('app.internal_api_key')返回的是config/app.php里写死的默认值'your-secret-key-here',而非.env中配置的真实密钥- 直接调用
env('INTERNAL_API_KEY')返回空字符串 - 两端配置目视比对完全一致,认证仍然失败
- 修改
.env之后不重启也不报任何错误,只是静默地继续用默认值
根因
ThinkPHP 解析 .env 时,每个 key 都会被自动冠以它所在 [SECTION] 头的名称作为前缀。来看一个典型的 .env 文件片段:
[APP]
SECRET_KEY = abc123
[LANG]
default_lang = zh-cn
INTERNAL_API_KEY = my-secret # ← 这行物理上跟在 [LANG] 后面!ThinkPHP 解析后实际存储的 key 映射关系是:
| .env 写法 | 实际存储 key |
|---|---|
| [APP] 下的 SECRET_KEY | APP.SECRET_KEY |
| [LANG] 下的 default_lang | LANG.DEFAULT_LANG |
| [LANG] 之后的 INTERNAL_API_KEY | LANG.INTERNAL_API_KEY |
问题就在第三行:INTERNAL_API_KEY 虽然从视觉上看像是"游离"在 section 之外的顶级 key,但 ThinkPHP 会把它归属到最后一个出现的 [SECTION] 名下——这里是 LANG。
于是 config/app.php 里的这行代码:
'internal_api_key' => env('INTERNAL_API_KEY', 'default'),查找的是无前缀的 INTERNAL_API_KEY,实际根本不存在,所以永远走默认值分支,而且不抛异常、不打警告、不报错。
.env 文件里没有显式 section 声明的行,会被 ThinkPHP 归入最近一个 [SECTION] 名下。在 [LANG] 后随手加一行 API_KEY = xxx,实际注册的是 LANG.API_KEY,不是 API_KEY。env('API_KEY') 永远查不到,只会拿到默认值,且全程无任何报错。
正解
方法 A(推荐):把 key 移到正确的 section 下,config 用完整前缀读取
# .env
[APP]
SECRET_KEY = abc123
INTERNAL_API_KEY = my-secret # ← 移到 [APP] 下
[LANG]
default_lang = zh-cn// config/app.php
'internal_api_key' => env('APP.INTERNAL_API_KEY', 'default'),方法 B:不动 .env,config 里补全实际前缀
// config/app.php
'internal_api_key' => env('LANG.INTERNAL_API_KEY', 'default'),适合 .env 不方便改(如多环境共享)的情况,但语义上很奇怪,不推荐。
验证方式
用命令行快速探测实际存储的 key:
php -r "
require 'vendor/autoload.php';
\$app = new \think\App();
\$app->initialize();
// 先用带前缀的方式确认值是否存在
echo env('APP.INTERNAL_API_KEY');
// 再确认 config 层能否正确读到
echo config('app.internal_api_key');
"ThinkPHP 的 env() 函数读 key 时,必须写 env('SECTION.KEY_NAME'),其中 SECTION 对应 .env 里的 [SECTION] 头(自动大写处理)。要么在 .env 里把 key 放到正确的 section,要么在 config/app.php 中用完整的带前缀写法。
附:think-multi-app 劫持单应用路由
同一次排查过程中还遇到了另一个静默炸弹:安装了 topthink/think-multi-app 包但项目是单应用结构,导致所有路由报"控制器不存在",route/app.php 中定义的路由完全不生效。
只需在 config/app.php 显式关掉自动多应用检测即可:
'auto_multi_app' => false,think-multi-app 只要安装进 vendor,默认就会启用自动多应用模式,不管项目结构是否真的多应用。
一句话外卖
ThinkPHP
.env里的每一行都要问自己"我在哪个[SECTION]下?"——不在任何 section 下不代表是顶级 key,而是归属于上一个 section,env()必须带完整前缀才能查到。