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

我最近添加了 .env.vault 对 PHP 的支持,并且我发现在使用 phpdotenv 时,开发和生产环境之间存在严重的不一致。
值可能会出现空白(糟糕!),并且load 的工作方式与其他 主要 dotenv 库不同。
幸运的是,修复方法很简单。
- 使用
$_SERVER- 不要使用$_ENV或getenv - 使用
safeLoad()- 不要使用.load()
让我们深入了解一下。
另外,我想说我知道维护像 phpdotenv 这样广泛嵌入的库是多么困难。库存在不一致的原因是有充分的历史原因。有时改变不一致会导致更糟糕的级联效应。
设置
安装 phpdotenv。
composer require vlucas/phpdotenv
创建一个 .env 文件。
HELLO="File"
然后使用每个可用的访问器加载你的 .env 文件,以一种能够输出 Hello File 的方式。
$_ENV$_SERVERgetenv
<?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 库 (node,ruby,python) 中,当 .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 应该变成类似 loadAndHaltIfMissingEnv,safeLoad 应该变成 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 开发愉快!
