CLI 下 env() 返回 null 的根本原因是未加载 .env 文件;ThinkPHP 8.0 仅在 HTTP 入口自动加载,CLI 需手动在根目录 think 脚本中调用 Dotenv::safeLoad(),并注意白名单、BOM 编码及调用时机。

CLI 和 HTTP 下 env() 返回值不一致,根本原因不是“读取逻辑不同”,而是 CLI 根本没加载 .env 文件——它压根没走那套流程。
为什么 CLI 下 env() 总是 null 或 false
ThinkPHP 8.0 默认只在 public/index.php(HTTP 入口)中自动加载 .env,而 think 命令入口(CLI)完全跳过这一步。这不是 bug,是设计选择:CLI 启动路径、用户权限、上下文都和 Web 不同,框架不猜,交给你显式控制。
-
php think migrate执行时,env('DB_NAME')返回false,数据库连接失败,就是典型表现 - 执行
php think env:list输出为空或变量值为null,说明.env没被识别 - 即使
.env文件存在且内容正确,只要没手动触发加载,env()就永远拿不到值
如何让 CLI 正确加载 .env 文件
必须在项目根目录下的 think 文件末尾(return 语句前)插入加载逻辑。这个文件不是 public/index.php,也不是 vendor/bin/think,而是你项目根目录下那个可执行的 think 脚本。
- 确认
__DIR__指向项目根目录(即含app/、config/、composer.json的目录),否则safeLoad()会找不到.env - 粘贴这段代码即可:
if (PHP_SAPI === 'cli') {<br> \Dotenv\Dotenv::createImmutable(__DIR__)->safeLoad();<br>} - 别用
load(),坚持用safeLoad():它对 UTF-8 BOM 更敏感,能帮你提前暴露编码问题 - 如果用了 Swoole 或自定义守护进程,同样需要在启动脚本里手动调用,不能依赖 HTTP 生命周期
为什么 HTTP 能读到但 CLI 还是读不到
常见干扰项比想象中多,尤其容易被忽略的是白名单机制和编码问题。
立即学习“PHP免费学习笔记(深入)”;
-
env('DB_HOST')返回null,但getenv('DB_HOST')有值?这是 ThinkPHP 的白名单限制:默认只放行APP_ENV、APP_DEBUG等少数键,自定义变量如DB_HOST被直接过滤。解决办法是改用env('DB_HOST', '127.0.0.1')(推荐),或提前调用\think\Env::setWhitelist(['DB_HOST']) -
.env用 Windows 记事本保存过?极大概率带了 UTF-8 BOM,导致safeLoad()静默失败。用 VS Code 或 Notepad++ 另存为 “UTF-8 无 BOM” 格式,再用file -i .env(Linux/macOS)验证 - 定时任务写成
/usr/bin/php /var/www/project/public/index.php?错。必须cd /var/www/project && /usr/bin/php think xxx,否则__DIR__指向错误,.env加载失败
env() 和 config() 的行为差异必须分清
别用 config('database.hostname') 有值,就误以为 env('DB_HOST') 也生效了。它们走的是两条路:
-
env()只查$_ENV、getenv()和已加载的.env内容,不经过配置合并 -
config()是最终结果:它把.env解析后的值、PHP 配置文件、运行时 set 合并在一起,所以即使env()失败,config()仍可能返回默认值(比如config/database.php里硬写的'hostname' => '127.0.0.1') - 调试时唯一可信的方式是
var_dump(env('DB_HOST')),而不是看config()输出
最常被忽略的其实是时机问题:在 bootstrap/app.php 或 config/*.php 里提前调 Env::get(),此时 .env 还没加载,必然返回 null。所有 env() 调用必须发生在 App::initialize() 之后,或者干脆统一放在配置文件里用 env('KEY', 'default') 形式延迟解析。



















