← 返回部落格 Mot

Mot

PHP dotenv 在開發和生產環境中不一致

我最近為 PHP 新增了 .env.vault 支援,並且在使用 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 [空白]

情境 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()

在其他 3 個主要的 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 應該變成類似 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

呼 💛🌴,我感覺好多了。

結論

總之,使用 $_SERVER,並使用 safeLoad 而不是 load。當使用 phpdotenv-vault 處理加密的 .env.vault 檔案時,也請執行相同的操作。

快樂地使用 PHP!

透過 RSS 訂閱 或追蹤我們的 @dotenvx 𝕏