PHP dotenv 在開發和生產環境中不一致
我最近為 PHP 新增了 .env.vault
支援,並且在使用 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 [空白]
。
情境 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 函式庫(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
。
呼 💛🌴,我感覺好多了。
結論
總之,使用 $_SERVER
,並使用 safeLoad
而不是 load
。當使用 phpdotenv-vault 處理加密的 .env.vault
檔案時,也請執行相同的操作。
快樂地使用 PHP!