Skip to content

$_ENV — 环境变量

概述

$_ENV 是 PHP 中的超全局变量,用于访问当前进程的环境变量。环境变量是操作系统向运行中的程序传递配置信息的一种机制,常用于存储数据库连接信息、API 密钥、应用密钥等敏感配置。在现代 PHP 开发中,$_ENV 配合 .env 文件已经成为管理应用配置的标准做法。

前置知识

在阅读本节之前,你需要了解:

  • 操作系统环境变量的概念
  • PHP 配置文件 php.inivariables_order 指令
  • .env 文件在项目配置中的作用
  • Composer 和 dotenv 库的基本使用

基础概念

什么是环境变量

环境变量是操作系统维护的键值对,所有运行在该系统上的进程都可以访问。在 PHP 中,环境变量可以来自以下渠道:

来源示例说明
操作系统PATHHOMEUSER系统级环境变量
Web 服务器DB_HOSTAPP_ENVApache/Nginx 配置中设置
Shell 脚本export DB_PASSWORD=secret启动脚本中设置
.env 文件APP_KEY=base64:xxx项目根目录的配置文件
Docker-e MYSQL_ROOT_PASSWORD=x容器启动参数

$_ENV 与 variables_order

$_ENV 是否可用取决于 php.ini 中的 variables_order 配置:

ini
; 默认值(注意:不包含 E)
variables_order = "GPCS"

; G = GET, P = POST, C = Cookie, S = Server, E = Environment
; 要让 $_ENV 可用,必须包含 E
variables_order = "EGPCS"

$_ENV 默认可能为空

在很多 PHP 安装中,variables_order 默认值为 "GPCS",不包含 E。这意味着 $_ENV 将为空数组。如果需要使用 $_ENV,需要修改 variables_order 配置,或使用 getenv() 函数(它不受此限制)。

语法与代码

基本使用

php
<?php
declare(strict_types=1);

// 检查 $_ENV 是否可用
if (empty($_ENV)) {
    echo '$_ENV 为空,请检查 variables_order 配置' . PHP_EOL;
    echo '当前 variables_order: ' . ini_get('variables_order') . PHP_EOL;
}

// 读取环境变量
$home = $_ENV['HOME'] ?? '/tmp';
$path = $_ENV['PATH'] ?? '';
$appEnv = $_ENV['APP_ENV'] ?? 'production';

echo "HOME: {$home}";
echo "APP_ENV: {$appEnv}";

使用 getenv() 替代 $_ENV

php
<?php
declare(strict_types=1);

/**
 * getenv() 不受 variables_order 限制
 * 是获取环境变量的推荐方式
 */

// getenv() 使用方式
$dbHost = getenv('DB_HOST');
$dbName = getenv('DB_NAME');
$dbUser = getenv('DB_USER');
$dbPass = getenv('DB_PASSWORD');

// getenv() 默认在变量不存在时返回 false
// 第二个参数 true(PHP 7.1+)使其在变量不存在时返回空字符串
$dbHost = getenv('DB_HOST', true); // 不存在时返回 '' 而非 false

// 安全的环境变量获取函数
function env(string $key, mixed $default = null): mixed
{
    $value = getenv($key);
    if ($value === false) {
        return $default;
    }

    // 自动转换布尔值
    return match (strtolower($value)) {
        'true', '(true)'  => true,
        'false', '(false)' => false,
        'null', '(null)'   => null,
        'empty', '(empty)' => '',
        default => $value,
    };
}

// 使用示例
$debug   = env('APP_DEBUG', false);
$env     = env('APP_ENV', 'production');
$dbPort  = env('DB_PORT', 3306);
$cacheEnabled = env('CACHE_ENABLED', true);

var_dump($debug);        // bool(true)
var_dump($env);          // string(10) "development"
var_dump($cacheEnabled); // bool(true)

手动解析 .env 文件

php
<?php
declare(strict_types=1);

/**
 * 简单的 .env 文件解析器
 * (生产环境推荐使用 vlucas/phpdotenv 库)
 */
class DotEnv
{
    public static function load(string $path): void
    {
        if (!file_exists($path) || !is_readable($path)) {
            return;
        }

        $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if ($lines === false) {
            return;
        }

        foreach ($lines as $line) {
            // 跳过注释
            if (str_starts_with(trim($line), '#')) {
                continue;
            }

            // 跳过无等号的行
            if (!str_contains($line, '=')) {
                continue;
            }

            // 解析键值对
            [$name, $value] = explode('=', $line, 2);
            $name  = trim($name);
            $value = trim($value);

            // 移除引号
            $value = trim($value, '"\'');

            // 设置为环境变量
            putenv("{$name}={$value}");
            $_ENV[$name] = $value;
        }
    }
}

// 使用示例
DotEnv::load(__DIR__ . '/.env');

// 现在可以通过 getenv() 和 $_ENV 访问
$dbHost = getenv('DB_HOST', true);
echo "数据库主机: {$dbHost}";

环境变量配置管理类

php
<?php
declare(strict_types=1);

/**
 * 环境配置管理类
 * 支持类型转换和默认值
 */
class EnvConfig
{
    private static ?array $cache = null;

    /**
     * 获取字符串环境变量
     */
    public static function string(string $key, string $default = ''): string
    {
        return (string)self::get($key, $default);
    }

    /**
     * 获取整数环境变量
     */
    public static function int(string $key, int $default = 0): int
    {
        $value = self::get($key, (string)$default);
        return is_numeric($value) ? (int)$value : $default;
    }

    /**
     * 获取布尔环境变量
     */
    public static function bool(string $key, bool $default = false): bool
    {
        $value = self::get($key, '');
        return filter_var($value, FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * 获取浮点数环境变量
     */
    public static function float(string $key, float $default = 0.0): float
    {
        $value = self::get($key, (string)$default);
        return is_numeric($value) ? (float)$value : $default;
    }

    /**
     * 获取数组环境变量(逗号分隔)
     */
    public static function array(string $key, array $default = []): array
    {
        $value = self::get($key, '');
        if ($value === '') {
            return $default;
        }
        return array_map('trim', explode(',', $value));
    }

    /**
     * 检查环境变量是否存在
     */
    public static function has(string $key): bool
    {
        return getenv($key) !== false;
    }

    private static function get(string $key, string $default): string
    {
        if (self::$cache === null) {
            self::$cache = [];
        }

        if (array_key_exists($key, self::$cache)) {
            return self::$cache[$key];
        }

        $value = getenv($key, true);
        if ($value === false || $value === '') {
            return $default;
        }

        self::$cache[$key] = $value;
        return $value;
    }
}

// 使用示例
$dbHost     = EnvConfig::string('DB_HOST', 'localhost');
$dbPort     = EnvConfig::int('DB_PORT', 3306);
$dbName     = EnvConfig::string('DB_NAME', 'myapp');
$debug      = EnvConfig::bool('APP_DEBUG', false);
$allowedOrigins = EnvConfig::array('CORS_ALLOWED_ORIGINS', ['http://localhost']);

详细说明

.env 文件格式

ini
# 应用配置
APP_NAME=MyApp
APP_ENV=development
APP_DEBUG=true
APP_KEY=base64:random_string_here

# 数据库配置
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret

# 缓存配置
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# 邮件配置
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=user@gmail.com
MAIL_PASSWORD=app_password

# 多值(逗号分隔)
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080

# 带引号的值(可选)
SITE_NAME="My Awesome Site"
LOGO_PATH='/assets/logo.png'

$_ENV vs getenv() 对比

特性$_ENVgetenv()
variables_order 限制受限(需包含 E不受限
默认返回值不存在时触发警告false(PHP 7.1+ 可返回 ''
性能直接数组访问,更快函数调用,略慢
可写性可直接赋值需要 putenv()
推荐程度需配合配置修改推荐

推荐 getenv()

在大多数情况下,推荐使用 getenv() 而非 $_ENV,因为 getenv() 不受 variables_order 配置的限制,更加可靠。

在不同环境中的环境变量设置

Apache 虚拟主机配置:

apache
<VirtualHost *:80>
    ServerName example.com
    SetEnv APP_ENV production
    SetEnv DB_HOST 192.168.1.100
</VirtualHost>

Nginx + PHP-FPM 配置:

nginx
server {
    listen 80;
    server_name example.com;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_param APP_ENV production;
        fastcgi_param DB_HOST 192.168.1.100;
        include fastcgi_params;
    }
}

Docker Compose 配置:

yaml
services:
  app:
    image: php:8.2-fpm
    environment:
      - APP_ENV=production
      - DB_HOST=mysql
      - DB_PASSWORD=secret
    env_file:
      - .env.production

系统环境变量(Linux):

bash
# 临时设置(仅当前 Shell)
export DB_HOST=localhost

# 持久设置(写入配置文件)
echo 'export DB_HOST=localhost' >> ~/.bashrc

# systemd 服务中设置
# /etc/systemd/system/php-app.service
[Service]
Environment="APP_ENV=production"
EnvironmentFile=/etc/php-app/env

注意事项

1. 不要将 .env 文件提交到版本控制

gitignore
# .gitignore
.env
.env.local
.env.*.local

2. .env 文件中的敏感信息

.env 文件通常包含数据库密码、API 密钥等敏感信息,需要确保:

  • 不提交到 Git
  • 服务器文件权限设置为仅应用进程可读(chmod 600 .env
  • 日志中不输出环境变量值

3. 进程间环境变量隔离

PHP-FPM 的不同进程池有独立的环境变量。修改 .env 文件后,需要重启 PHP-FPM 才能生效(如果使用 putenv() 加载的话)。

4. 环境变量的大小限制

不同操作系统对环境变量有大小限制:

  • Linux:单个变量约 128KB,总量约 32MB
  • Windows:单个变量约 32KB

最佳实践

  1. 使用 getenv():不受 variables_order 限制
  2. 使用 dotenv 库:生产环境推荐使用 vlucas/phpdotenv
  3. 环境变量命名规范:使用大写字母和下划线(如 DB_HOST
  4. 类型转换:对环境变量值进行适当的类型转换
  5. .env 文件模板:提供 .env.example 作为模板,不包含真实密钥
  6. 不同环境分离:开发、测试、生产使用不同的 .env 文件
  7. 永远不输出密钥:日志和错误信息中不要输出环境变量的值
php
<?php
declare(strict_types=1);

// .env.example(提交到版本控制)
// APP_NAME=MyApp
// APP_ENV=development
// APP_DEBUG=true
// DB_HOST=127.0.0.1
// DB_PASSWORD=your_password_here

// .env(不提交,包含真实值)
// APP_NAME=MyApp
// APP_ENV=production
// APP_DEBUG=false
// DB_HOST=192.168.1.100
// DB_PASSWORD=actual_secret_password

下一节

下一节将详细介绍 $_COOKIE 超全局变量,了解 Cookie 的工作原理、作用域和有效期管理。

参考链接