← 返回博客 Mot

Mot

PHP dotenv 在开发和生产环境中不一致

我最近添加了 .env.vault 对 PHP 的支持,并且我发现在使用 phpdotenv 时,开发和生产环境之间存在严重的不一致。

值可能会出现空白(糟糕!),并且load 的工作方式与其他 主要 dotenv 库不同。

幸运的是,修复方法很简单。

  • 使用 $_SERVER - 不要使用 $_ENVgetenv
  • 使用 safeLoad() - 不要使用 .load()

让我们深入了解一下。

另外,我想说我知道维护像 phpdotenv 这样广泛嵌入的库是多么困难。库存在不一致的原因是有充分的历史原因。有时改变不一致会导致更糟糕的级联效应。

设置

安装 phpdotenv

composer require vlucas/phpdotenv

创建一个 .env 文件。

HELLO="File"

然后使用每个可用的访问器加载你的 .env 文件,以一种能够输出 Hello File 的方式。

  1. $_ENV
  2. $_SERVER
  3. getenv
<?php
// example1.php
require 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$env_hello = $_ENV['HELLO'];
$server_hello = $_SERVER['HELLO'];
$getenv_hello = getenv('HELLO');

echo "ENV:    Hello {$env_hello}";
echo "\n";
echo "SERVER: Hello {$server_hello}";
echo "\n";
echo "getenv: Hello {$getenv_hello}";

好的,让我们运行一些场景,演示这些不一致之处。

场景

场景 1 - getenv 缺少值

在第一个场景中,getenv 的值返回为空。

<?php
// example1.php
require 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$env_hello = $_ENV['HELLO'];
$server_hello = $_SERVER['HELLO'];
$getenv_hello = getenv('HELLO');

echo "ENV:    Hello {$env_hello}";
echo "\n";
echo "SERVER: Hello {$server_hello}";
echo "\n";
echo "getenv: Hello {$getenv_hello}";
$ php example1.php
ENV:    Hello File
SERVER: Hello File
getenv: Hello

getenv 返回 Hello [blank]

场景 2 - createUnsafeImmutable 不线程安全

在第二个场景中,我们删除了线程安全性。

createImmutable 更改为 createUnsafeImmutable 以便将数据填充到 getenv 中。

<?php
// example2
require 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

$env_hello = $_ENV['HELLO'];
$server_hello = $_SERVER['HELLO'];
$getenv_hello = getenv('HELLO');

echo "ENV:    Hello {$env_hello}";
echo "\n";
echo "SERVER: Hello {$server_hello}";
echo "\n";
echo "getenv: Hello {$getenv_hello}";
$ php example2.php
ENV:    Hello File
SERVER: Hello File
getenv: Hello File

这有效。getenv 现在正确地返回 Hello File,但它 不线程安全 - 对任何生产应用程序来说都是非常危险的!

因此,让我们将其切换回 createImmutable 并尝试其他方法。

场景 3 - $_ENV 缺少值

在第三个场景中,$_ENV 返回为空。

通过预设 HELLO=Server 来模拟服务器上已设置的环境变量的行为。

<?php
// example1.php
require 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$env_hello = $_ENV['HELLO'];
$server_hello = $_SERVER['HELLO'];
$getenv_hello = getenv('HELLO');

echo "ENV:    Hello {$env_hello}";
echo "\n";
echo "SERVER: Hello {$server_hello}";
echo "\n";
echo "getenv: Hello {$getenv_hello}";
$ HELLO="Server" php example1.php
PHP Warning:  Undefined array key "HELLO" in /Users/scottmotte/Code/dotenv-org/examples/dotenv-blog/2023-11-07/example1.php on line 8
Warning: Undefined array key "HELLO" in /Users/scottmotte/Code/dotenv-org/examples/dotenv-blog/2023-11-07/example1.php on line 8

ENV:    Hello
SERVER: Hello Server
getenv: Hello Server

$_ENV 为空(并且我们得到了警告)!这在开发和生产环境之间的行为不一致。

但是 $_SERVER 在所有三个场景中都一致。以后使用它。很简单。

load() vs safeLoad()

在其他三个主要的 dotenv 库 (noderubypython) 中,当 .env 文件不存在时,load 方法会静默地不做任何事情。

这是有充分理由的。你的 .env 文件没有提交到代码中。因此,当你将代码部署到生产环境 (或 ci) 时,没有 .env 文件存在。预期的是服务器已经在内存中拥有你的环境变量。

让我们看看 phpdotenv 在这种情况下会做什么。

删除你的 .env 文件,然后再次运行脚本。

rm .env
$ php example1.php
PHP Fatal error:  Uncaught Dotenv\Exception\InvalidPathException: Unable to read any of the environment file(s) at [../.env]. in /../vendor/vlucas/phpdotenv/src/Store/FileStore.php:68
Stack trace:
...

它会发出一个堆栈跟踪错误,使你的应用程序崩溃!

这真的让我很惊讶,因为这是一个非常危险的默认设置。它鼓励开发人员将他们的 .env 文件提交到代码中以解决问题。

幸运的是,修复方法也很简单。使用 safeLoad 而不是 load

但是,根据我的经验,一个刚接触 .env 文件的开发人员不会有经验在这里正确地使用 safeLoad。他们更有可能将他们的 .env 文件提交到代码中并继续他们的工作。我承认我不了解此处做出此决定的历史背景,但我目前认为这种命名模式应该颠倒。load 应该变成类似 loadAndHaltIfMissingEnvsafeLoad 应该变成 load

无论如何,让我们看看修复方法。

<?php
// example3
require 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad(); // <--- use safeLoad

$env_hello = $_ENV['HELLO'];
$server_hello = $_SERVER['HELLO'];
$getenv_hello = getenv('HELLO');

echo "ENV:    Hello {$env_hello}";
echo "\n";
echo "SERVER: Hello {$server_hello}";
echo "\n";
echo "getenv: Hello {$getenv_hello}";

$ php example3.php
ENV:    Hello
SERVER: Hello
getenv: Hello

所有空白值都没有堆栈跟踪,应该是这样的。

让我们再次模拟生产环境。

$ HELLO="Server" php example3.php
ENV:    Hello
SERVER: Hello Server
getenv: Hello Server

$_SERVER 正确地返回 Hello Server

Whew 💛🌴,感觉好多了。

结论

总之,使用 $_SERVER,并使用 safeLoad 而不是 load。在使用 phpdotenv-vault 和加密的 .env.vault 文件时,也要这样做。

祝你 PHP 开发愉快!

通过 RSS 订阅 或关注我们 @dotenvx 𝕏