学完这篇你能得到:一套从 `.env` 到多环境切换、再到配置中心的可落地 PHP 配置方案,知道每一层该放什么、什么时候该升级到下一层。
第一步:先把配置分成三类,别一锅炖
配置乱,八成是因为三种东西混在同一个文件里。按「谁会改它」来分:
| 类型 | 例子 | 放哪 |
|---|
| 环境相关 + 敏感 | 数据库密码、Redis 地址、第三方 API Key、调试开关 | `.env` |
| 业务可调 | 站点名称、每页条数、功能开关、套餐权益 | 数据库(后台可视化配置) |
| 代码常量 | 路由表、表名映射、钩子名单 | PHP 数组文件 |
判断口诀:**换个服务器就要改的进 `.env`;运营想改的进数据库;只有发版才改的进代码。**
像 Clara BBS 这类无框架系统走的就是后两条路——后台「系统设置」里改一项保存即生效,因为它压根没有编译缓存层,不需要你手动清缓存。插件自己的配置则放在 `content/plugins` 对应目录里。
第二步:写一个 20 行的 .env 解析器
没 Composer 也能用,手写即可:
function loadEnv(string $file): void {
if (!is_readable($file)) return;
foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
[$k, $v] = array_pad(explode('=', $line, 2), 2, '');
$k = trim($k); $v = trim($v);
if (strlen($v) > 1 && ($v[0] === '"' || $v[0] === "'") && $v[0] === substr($v, -1)) {
$v = substr($v, 1, -1);
}
if ($k !== '' && getenv($k) === false) {
putenv("$k=$v");
$_ENV[$k] = $v;
}
}
}
loadEnv(__DIR__ . '/../.env'); // 注意:在 web 根目录之外
注意:`.env` 必须放在 web 根目录之外。放在根目录又没配 Nginx 规则,别人直接访问 `https://你的站/.env` 就把数据库密码下载走了。兜底规则:`location ~ /\.env { deny all; }`。
第三步:多环境 = 公共默认 + 环境覆盖
别维护三份完整配置,改一个字段要改三遍。用「一份默认 + 每环境差异覆盖」:
config/
app.php # 公共默认值
app.local.php # 本地开发覆盖
app.prod.php # 生产覆盖
$env = getenv('APP_ENV') ?: 'prod';
$file = __DIR__ . "/config/app.$env.php";
$config = array_replace_recursive(
require __DIR__ . '/config/app.php',
is_file($file) ? require $file : []
);
// 环境变量优先级最高,方便容器里临时覆盖
define('DB_HOST', getenv('DB_HOST') ?: $config['db']['host']);
优先级记住一条:环境变量 > 环境覆盖文件 > 公共默认。部署时只传 `APP_ENV=prod`,其余靠文件。
注意:`.env` 里写 `DEBUG=false`,取出来是字符串 `"false"`,在 PHP 里是 `true`(非空字符串为真)。布尔值一定要过 `filter_var($v, FILTER_VALIDATE_BOOLEAN)`,否则线上永远开着调试模式。
第四步:什么时候才需要配置中心
单机小站不需要,纯属自找麻烦。出现下面任意一条再上:机器超过 3 台、改配置要逐台登服务器、需要审计「谁在什么时候改了哪项」。
轻量做法不用引入中间件,HTTP 拉 JSON + 本地快照就够:
function remoteConfig(string $url, string $cacheFile, int $ttl = 300): array {
$cached = is_file($cacheFile)
? json_decode((string)file_get_contents($cacheFile), true) : null;
if ($cached && filemtime($cacheFile) + $ttl > time()) return $cached;
$ctx = stream_context_create(['http' => ['timeout' => 2, 'ignore_errors' => true]]);
$raw = @file_get_contents($url, false, $ctx);
if ($raw && is_array($data = json_decode($raw, true))) {
@file_put_contents($cacheFile, $raw, LOCK_EX);
return $data;
}
return $cached ?: []; // 关键:拉不到就降级,绝不抛异常
}
注意:配置中心是旁路,不是主链路。它挂了站点也得能跑——必须有本地快照兜底,超时设 2 秒以内,绝不能在请求里同步等它。
第五步:绕不开的几个坑
- `.env` 进 git:`.gitignore` 加一行,同时提交一份 `.env.example` 给同事抄。
- 权限过大:`chmod 640 .env`,属主设为 PHP 运行用户。
- 改配置不生效:如果开了 OPcache,注意 `opcache.validate_timestamps` 是否被关;数组配置文件靠 OPcache 加速,改完要 reload。
- 敏感配置落日志:打印 `$_ENV` 排错时记得脱敏,别把 Key 写进日志文件。
- 升级后配置缺字段:像 Clara BBS 覆盖上传新版本后,要去后台「系统工具→数据库升级」执行一次增量 DDL,新增的配置列才补齐,是幂等的,重复执行无副作用。
小结
- 配置按「谁会改」分三类:敏感进 `.env`,业务进数据库,常量进代码。
- `.env` 一律放 web 根目录之外,并配 deny 规则;布尔值必须显式转类型。
- 多环境用「公共默认 + 环境覆盖 + 环境变量兜底」,别维护多份完整文件。
- 单机别上配置中心;上了就必须有本地快照降级和短超时。
- 改配置不生效,先查 OPcache,再查是否真的加载了新的配置文件。