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
$_SERVER
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 库 (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 开发愉快!