Symfony 中环境变量 fallback 必须用 %env(DEFAULT:val:VAR)% 语法,支持类型转换如 %env(int:DEFAULT:3000:VAR)%;bind 可统一注入默认值;.env 不支持 fallback;运行时 getenv() 应避免。

services.yaml 里用 %env()% 语法设默认值
环境变量本身没有“默认值”概念,但 Symfony 允许你在引用时 fallback 到一个字面值,只要用 %env(DEFAULT:foo:BAR)% 这种写法。注意:必须带 DEFAULT: 前缀,且中间的 foo 是类型转换器(可选),BAR 是 fallback 字符串。
常见组合:
-
%env(DEFAULT:localhost:DATABASE_HOST)%→ 变量未定义时用localhost -
%env(int:DEFAULT:3000:API_TIMEOUT_MS)%→ 先 fallback 再转 int -
%env(bool:DEFAULT:false:APP_DEBUG)%→ fallback 后按 bool 解析("false"、"0"、空字符串都会变成false)
⚠️ 错误写法:%env(DATABASE_HOST)% 不会 fallback;%env(DEFAULT:DATABASE_HOST)% 缺少冒号分隔,直接报错。
PHP 8+ 构造函数参数默认值 + bind 配合使用
当服务构造函数用了命名参数,你可以在 services.yaml 中用 bind 显式指定 fallback,比硬写在 PHP 里更灵活,也避免污染业务逻辑。
例如:
services:
_defaults:
bind:
string $baseUrl: '%env(DEFAULT:https://api.example.com:API_BASE_URL)%'
int $timeout: '%env(int:DEFAULT:5000:API_TIMEOUT_MS)%'
这样所有自动装配了 $baseUrl 或 $timeout 的服务,都会统一 fallback,无需每个类重复写默认值。
⚠️ 注意:bind 的键名必须和构造函数参数名完全一致(包括类型提示),且只对启用了 autowire: true 的服务生效。
.env 文件里不能写 DEFAULT=xxx 这种 fallback
.env 文件本身不支持 fallback 语法。它只是键值对的静态源,APP_ENV=dev 就是 dev,没定义就是空字符串或 getenv() 返回 false。所有 fallback 行为必须由 Symfony 容器在解析 %env()% 时完成。
所以别在 .env 里写类似 DB_HOST=${DB_HOST:-localhost} —— 这是 symfony/dotenv 支持的变量展开,但它只用于加载阶段,不参与容器参数绑定的 fallback 逻辑。两者作用域不同,混用容易误判。
真正要控制 fallback,只在 services.yaml 或 bind 规则里做。
运行时读 getenv() 不推荐,fallback 更难维护
虽然你可以手动在服务里调用 getenv('FOO') ?: 'default',但这绕过了容器管理,导致:
- 无法被缓存编译后的容器(因为值在运行时才确定)
- 测试困难:mock 环境变量不如 mock 参数直观
- 类型不安全:
getenv()总是返回 string,bool/int 转换得自己写
除非是极少数需要动态计算的场景(比如根据当前时间生成密钥后缀),否则一律用 %env(DEFAULT:...)%。它在容器编译期就确定值,性能更好,也更容易被 IDE 和静态分析工具识别。
最易忽略的一点:DEFAULT fallback 不会触发 resolve:,所以像 %env(resolve:DEFAULT:foo:BAR)% 是非法语法 —— resolve: 和 DEFAULT: 不能共存。需要 resolve 的变量,必须确保它真实存在。


















