环境变量永远优先于.env文件,系统级或进程级环境变量(如app_env=prod)直接写入$_env和$_server,dotenv不覆盖;loadenv()仅在app_env未定义时按顺序加载.env→.env.local→.env.$app_env→.env.$app_env.local;%env(resolve:var)%解析最终$_env中的值,非.env原始字符串。

环境变量永远优先于.env文件
系统级或进程级环境变量(如 APP_ENV=prod)在 Symfony 启动时直接写入 $_ENV 和 $_SERVER,Dotenv 加载器根本不会覆盖它们。也就是说,哪怕 .env 里写了 APP_ENV=dev,只要你在 shell 里执行了 APP_ENV=prod php bin/console cache:clear,Symfony 就认 prod —— 不会去读 .env,也不会加载 .env.prod。
常见错误现象:
-
bin/console debug:container --env-var=DATABASE_URL显示空值,但.env.prod.local确实存在 -
echo $_ENV['APP_ENV']输出dev,而你刚在终端敲了APP_ENV=prod php bin/console ...
这通常是因为 Web 服务器(如 Apache 或 Nginx)没把环境变量透传给 PHP 进程,或者 CLI 下用了别名/函数封装导致变量未生效(比如 alias console='php bin/console' 会丢掉前置变量)。
为什么 loadEnv() 不总起作用
loadEnv() 是 Symfony 的启动入口函数,它只在 $_ENV['APP_ENV'] 为空或未定义时,才按顺序尝试加载 .env → .env.local → .env.$APP_ENV → .env.$APP_ENV.local。一旦 APP_ENV 已被设好,它就跳过整个 Dotenv 流程。
所以这些情况会让 .env.* 文件完全失效:
- Web 服务器配置中用
SetEnv APP_ENV prod(Apache)或fastcgi_param APP_ENV prod(Nginx)——loadEnv()不触发 - Docker 容器启动时通过
environment:或-e APP_ENV=prod设置——loadEnv()不触发 - PHP-FPM pool 配置里写了
env[APP_ENV] = prod——loadEnv()不触发
此时你改 .env.prod.local 没用,除非手动删掉所有环境变量再试(不推荐)。
%env(resolve:VAR)% 解析的是哪个值
它解析的是最终写入 $_ENV 的那个值,不是 .env 文件里的原始字符串。也就是说,解析链是:系统环境变量 → .env.* 文件(仅当 APP_ENV 未设时)→ resolve: 展开逻辑(默认值、命令替换等)。
例如:
-
APP_ENV=prod且.env.prod.local有DB_HOST=prod-db:解析结果为prod-db -
APP_ENV=prod且系统已设DB_HOST=override-host:解析结果为override-host,.env.prod.local被跳过 -
APP_ENV未设,但.env有DB_HOST=localhost,.env.local有DB_HOST=127.0.0.1:解析结果为127.0.0.1
注意:%env(DB_HOST)%(无 resolve:)只是字面量替换,不会做任何展开,也不查环境变量,它会被当成纯字符串塞进配置里。
生产部署时最易忽略的点
线上机器上,APP_ENV=prod 通常由 Web 服务器或 systemd service 文件设置,但 dotenv:dump 生成的 .env.local.php 只包含 .env.* 文件的内容,不包含系统环境变量。这意味着:如果你依赖 export DB_PASSWORD="xxx" 这类动态注入,.env.local.php 里根本没有它。
所以真正安全的做法是:
- 所有敏感值(密码、密钥)只放在系统环境变量或 secrets manager 中,不写进任何
.env.*文件 - 非敏感默认值(如
APP_ENV=prod)可写进.env,但必须确保它不会被提交到 Git - 用
bin/console debug:dotenv在目标环境运行一次,确认哪些变量来自环境、哪些来自文件
别假设 .env.prod.local 一定生效——先看 APP_ENV 是怎么来的,再看 $_ENV 里实际有什么。











