线上 env() 返回 null 的根本原因是 CLI 或自定义入口未加载 .env,ThinkPHP 默认仅在 public/index.php 中加载;需在 think 文件中手动 safeLoad,并检查编码、格式、白名单及配置缓存。

本地有值、线上为空,90% 是因为线上没加载 .env,不是配置写错了。 ThinkPHP 默认只在 Web 入口(public/index.php)中加载 .env,而线上部署常走 CLI 启动(如 php think run、Swoole、Workerman)、或通过 Nginx + PHP-FPM 直接调用入口但跳过了 .env 加载逻辑。它不报错,只是静默跳过——你改了 DB_HOST=prod-db,env('DB_HOST') 依然返回 null。
为什么线上 env() 返回 null,但本地能读到
根本差异在加载时机和上下文:
- 本地开发通常用
php think serve或 Apache/Nginx 访问public/,入口是public/index.php,里面默认会调用Dotenv::createImmutable(__DIR__.'/..')->safeLoad() - 线上常用
php think run、php start.php或 Swoole 自定义入口,这些路径**不经过public/index.php**,.env完全没被触发加载 - 更隐蔽的是:Docker 或 CI/CD 中若用
php -r "require 'start.php';"启动,也绕过了所有自动加载逻辑 -
env()函数本身不负责加载,只查$_ENV和getenv();线上$_ENV为空,getenv()又受php.ini的variables_order限制(常为"GPCS",不含E),双重失效
CLI 模式下必须手动加载 .env(ThinkPHP 8.0+)
打开项目根目录下的 think 文件(不是 public/index.php),在 return 语句前插入:
if (PHP_SAPI === 'cli') {
$dotenv = \Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();
}
注意三点:
立即学习“PHP免费学习笔记(深入)”;
-
__DIR__必须指向项目根目录(含app/、config/、composer.json),不是public/或vendor/ - 别用
load(),用safeLoad()——文件不存在或语法错也不中断脚本,方便排查 - 如果线上用定时任务(
cron),命令必须写成cd /path/to/project && php think migrate,不能直接用绝对路径调php /path/to/project/public/index.php
.env 文件本身在线上容易踩的坑
即使加载代码写了,.env 仍可能被静默忽略:
- 编码必须是
UTF-8 无 BOM:Windows 记事本默认带 BOM,VS Code 要选 “Save with Encoding → UTF-8”,Linux 下可用file -i .env验证 - 格式严格:
DB_HOST=prod-db✅,DB_HOST = prod-db❌(等号两侧不能有空格) - 值含特殊字符(如
@、!)必须加单引号:DB_PWD='p@ss!2026'✅ - 变量名必须全大写+下划线:
REDIS_URL✅,redis_url或REDIS-URL❌ - 线上环境常禁用
.env(安全策略),此时env()全部失效,但config()仍可用——因为配置已从config/database.php等文件加载完毕
验证线上是否真生效,别只信 dump(env('DB_HOST'))
在命令类的 handle() 开头加这两行:
var_dump($_ENV['DB_HOST'] ?? 'not in $_ENV');
var_dump(getenv('DB_HOST'));
结果含义:
- 两个都为
false或'not in $_ENV'→.env没加载(检查think文件、路径、编码) -
$_ENV有值但getenv()为false→php.ini的variables_order缺E,需改成"EGPCS" - 两者都有值,但
env('DB_HOST')还是null→ 白名单问题:Env::get()默认只放行APP_ENV、APP_DEBUG,自定义键如DB_HOST需提前设白名单,或直接用env('DB_HOST', '127.0.0.1')(推荐)
最易被忽略的是:缓存。线上改完 .env 和加载逻辑后,必须执行 php think clear:config 或删掉 runtime/config.php,否则旧快照一直生效。



















