4、环境一致性:开发/测试/生产环境差异、环境变量管理、.env文件最佳实践
环境不一致,是PHP项目部署中最常见的坑。
我见过太多团队,开发环境跑得好好的,一上测试就报错。到了生产环境,直接崩了。说白了,就是环境差异在作祟。今天咱们就聊聊怎么搞定这件事。
4.1 环境差异到底差在哪?
先说说三个环境的核心差异。我个人习惯把它们分成三类:
| 差异类型 | 开发环境 | 测试环境 | 生产环境 |
|---|---|---|---|
| 数据库配置 | 本地MySQL,root无密码 | 独立数据库,有测试账号 | 主从集群,只读/写入分离 |
| 缓存服务 | Redis单机,无密码 | Redis单机,有密码 | Redis哨兵集群 |
| 调试模式 | 开启,显示错误详情 | 开启,但记录日志 | 关闭,只记录错误日志 |
| 第三方API | 沙箱环境 | 沙箱环境 | 正式环境 |
你想想看,如果把这些配置硬编码在代码里,每次部署都要手动改一遍。改漏一个,就是事故。
⚠️ 我曾经踩过的坑:
有一次上线前,同事忘了把调试模式关掉。结果生产环境直接暴露了数据库密码。嗯,从那以后,我再也不敢把配置写死在代码里了。
4.2 环境变量管理:从硬编码到动态配置
解决环境差异的核心思路,就是「把配置从代码中抽离出来」。环境变量就是最好的方案。
PHP里获取环境变量很简单:
<?php
// 获取环境变量
$dbHost = getenv('DB_HOST');
$dbName = getenv('DB_NAME');
$dbUser = getenv('DB_USER');
$dbPass = getenv('DB_PASS');
// 设置默认值
$dbHost = getenv('DB_HOST') ?: 'localhost';
?>
但这里有个问题:开发环境怎么设置这些变量?
我建议用三种方式配合:
- 系统环境变量:生产环境用,通过运维工具统一管理
- PHP-FPM 配置:在
pool.d/www.conf里设置env[KEY] = value - .env 文件:开发环境用,方便本地调试
4.3 .env 文件最佳实践
.env 文件说白了就是个「本地配置管家」。我见过很多团队用不好它,这里分享几个经验。
4.3.1 文件结构规范
# .env.example - 提交到版本库的模板
APP_ENV=development
APP_DEBUG=true
APP_URL=http://localhost:8080
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
# .env - 实际使用的文件,不提交到版本库
APP_ENV=development
APP_DEBUG=true
APP_URL=http://localhost:8080
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=myapp_dev
DB_USERNAME=dev_user
DB_PASSWORD=dev_pass_123
💡 核心原则:
- .env.example 提交到 Git,作为配置模板
- .env 加入 .gitignore,永远不提交
- 生产环境用系统环境变量,不用 .env 文件
4.3.2 加载 .env 文件的正确姿势
我个人习惯用 vlucas/phpdotenv 这个库。但要注意加载顺序:
<?php
// bootstrap.php - 应用入口
require_once __DIR__ . '/vendor/autoload.php';
use Dotenv\Dotenv;
// 1. 先加载 .env 文件(开发环境)
if (file_exists(__DIR__ . '/.env')) {
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
}
// 2. 系统环境变量优先级更高
// 如果系统已经设置了 DB_HOST,就用系统的
$dbHost = getenv('DB_HOST') ?: $_ENV['DB_HOST'] ?? 'localhost';
// 3. 验证必要变量是否存在
$dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS']);
?>
📌 避坑指南:
我曾经遇到过 .env 文件被意外提交到 Git 仓库的情况。建议在 CI 流程里加一步检查:如果检测到 .env 文件被提交,直接阻断构建。
4.4 环境检测与自动适配
有时候我们需要在代码里判断当前环境。我建议用统一的环境变量来标识:
<?php
// config/app.php
return [
'env' => getenv('APP_ENV') ?: 'production',
'debug' => filter_var(
getenv('APP_DEBUG'),
FILTER_VALIDATE_BOOLEAN
),
'database' => [
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => getenv('DB_HOST'),
'database' => getenv('DB_DATABASE'),
'username' => getenv('DB_USERNAME'),
'password' => getenv('DB_PASSWORD'),
'charset' => 'utf8mb4',
// 生产环境用持久连接
'persistent' => getenv('APP_ENV') === 'production',
],
],
],
// 根据环境加载不同配置
'cache' => [
'default' => getenv('APP_ENV') === 'production'
? 'redis'
: 'file',
],
];
?>
4.5 部署时的环境变量注入
生产环境我建议用 CI/CD 工具来注入环境变量。以 GitHub Actions 为例:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Production
env:
APP_ENV: production
APP_DEBUG: false
DB_HOST: ${{ secrets.DB_HOST }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
run: |
# 部署脚本
rsync -avz --delete ./ user@server:/var/www/app
ssh user@server "cd /var/www/app && \
echo 'APP_ENV=$APP_ENV' > .env && \
echo 'DB_HOST=$DB_HOST' >> .env && \
composer install --no-dev --optimize-autoloader"
⚠️ 安全提醒:
敏感信息(数据库密码、API密钥等)一定要用 CI/CD 的 Secrets 功能,不要明文写在配置文件里。我见过有人把生产数据库密码写在 .env.example 里提交到 GitHub,结果被爬虫抓取,数据库被删了个精光。
4.6 环境一致性检查清单
最后,分享一个我每次部署前都会检查的清单:
- PHP 版本:开发机和服务器版本是否一致?
- 扩展列表:生产环境有没有漏装某个扩展?
- 时区设置:date.timezone 是否统一?
- 内存限制:memory_limit 是否足够?
- 文件上传:upload_max_filesize 和 post_max_size 是否匹配?
- 错误报告:生产环境是否关闭了 display_errors?
- OPcache:生产环境是否开启了 OPcache?
嗯,环境一致性说白了就一句话:配置不写死,环境不混用,变量统一管。做到这三点,你的部署流程至少能减少一半的故障。
下一章咱们聊聊自动化部署流水线的搭建,到时候会用到今天讲的环境变量管理技巧。