From d2ee59bd65474ed040afb24d70abc52ed39f8c8e Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Sun, 2 Nov 2025 21:11:29 +0100 Subject: [PATCH] fix: Fix environment variables not being captured correctly in PHP-FPM - Fix priority order in loadSystemEnvironment() to check and first - Add dynamic fallback in Environment::get() to handle variables set after initialization - Ensure all environment variables are captured during bootstrap, including those set dynamically by PHP-FPM/FastCGI Fixes issue where environment variables like RAPIDMAIL_USERNAME and RAPIDMAIL_PASSWORD were missing during bootstrap but available later in request processing. --- src/Framework/Config/EncryptedEnvLoader.php | 61 ++++++++++++++------- src/Framework/Config/Environment.php | 45 ++++++++++++--- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/Framework/Config/EncryptedEnvLoader.php b/src/Framework/Config/EncryptedEnvLoader.php index 46bc654e..71f4b05a 100644 --- a/src/Framework/Config/EncryptedEnvLoader.php +++ b/src/Framework/Config/EncryptedEnvLoader.php @@ -151,35 +151,54 @@ final readonly class EncryptedEnvLoader * Load variables from system environment (getenv(), $_ENV, $_SERVER) * * Priority order optimized for PHP-FPM compatibility: - * 1. getenv() - Works in PHP-FPM, reads from actual process environment - * 2. $_ENV - May be empty in PHP-FPM - * 3. $_SERVER - May contain additional vars from web server + * 1. $_ENV - Contains environment variables set by PHP-FPM/FastCGI + * 2. $_SERVER - May contain additional vars from web server (including HTTP_* headers) + * 3. getenv() - Fallback to read from actual process environment + * + * IMPORTANT: In PHP-FPM, some environment variables are set dynamically + * during request processing, so we must check $_ENV and $_SERVER first. */ private function loadSystemEnvironment(): array { $variables = []; - // 1. Load from getenv() - THIS WORKS IN PHP-FPM! - // In PHP-FPM, $_ENV and $_SERVER are empty by default. - // getenv() reads from actual process environment, unlike $_ENV + // 1. Load from $_ENV first (contains dynamically set vars in PHP-FPM) + // In PHP-FPM, environment variables are often set in $_ENV during request processing + foreach ($_ENV as $key => $value) { + // Only include actual environment variables, not all superglobals + // Filter out HTTP_* headers and other non-env vars + if (is_string($key) && is_string($value) && !str_starts_with($key, 'HTTP_')) { + $variables[$key] = $value; + } + } + + // 2. Load from $_SERVER (may contain additional vars from web server) + // Filter to only include non-HTTP variables that look like environment variables + foreach ($_SERVER as $key => $value) { + if (!isset($variables[$key]) && + is_string($key) && + is_string($value) && + !str_starts_with($key, 'HTTP_') && + !in_array($key, ['GATEWAY_INTERFACE', 'SERVER_SOFTWARE', 'SERVER_NAME', 'SERVER_ADDR', + 'SERVER_PORT', 'REQUEST_URI', 'REQUEST_METHOD', 'QUERY_STRING', + 'CONTENT_TYPE', 'CONTENT_LENGTH', 'SCRIPT_NAME', 'SCRIPT_FILENAME', + 'PATH_INFO', 'FCGI_ROLE', 'REDIRECT_STATUS', 'REQUEST_TIME', + 'REQUEST_TIME_FLOAT', 'DOCUMENT_ROOT', 'DOCUMENT_URI', + 'REMOTE_ADDR', 'REMOTE_PORT', 'REMOTE_USER'], true)) { + $variables[$key] = $value; + } + } + + // 3. Load from getenv() - Fallback for process environment variables + // In PHP-FPM, $_ENV and $_SERVER are often populated, but getenv() provides process-level vars $allEnvVars = getenv(); if ($allEnvVars !== false) { foreach ($allEnvVars as $key => $value) { - $variables[$key] = $value; - } - } - - // 2. Load from $_ENV (may be empty in PHP-FPM) - foreach ($_ENV as $key => $value) { - if (!isset($variables[$key])) { - $variables[$key] = $value; - } - } - - // 3. Load from $_SERVER (may contain additional vars from web server) - foreach ($_SERVER as $key => $value) { - if (!isset($variables[$key]) && is_string($value)) { - $variables[$key] = $value; + // Only override if not already set from $_ENV or $_SERVER + // This ensures dynamic vars from PHP-FPM take precedence + if (!isset($variables[$key])) { + $variables[$key] = $value; + } } } diff --git a/src/Framework/Config/Environment.php b/src/Framework/Config/Environment.php index 7e970260..bcdbb543 100644 --- a/src/Framework/Config/Environment.php +++ b/src/Framework/Config/Environment.php @@ -23,7 +23,7 @@ final readonly class Environment { $key = $this->keyToString($key); - // 1. Check if direct env var exists + // 1. Check if direct env var exists in internal array if (isset($this->variables[$key])) { return $this->variables[$key]; } @@ -34,10 +34,45 @@ final readonly class Environment return $secretValue; } - // 3. Return default + // 3. Fallback: Check system environment dynamically + // This handles cases where environment variables are set after Environment initialization + // (common in PHP-FPM where vars may be set during request processing) + $systemValue = $this->getFromSystemEnvironment($key); + if ($systemValue !== null) { + return $systemValue; + } + + // 4. Return default return $default; } + /** + * Get variable from system environment as fallback + * + * This ensures we can access environment variables that were set + * after Environment initialization (e.g., by PHP-FPM/FastCGI) + */ + private function getFromSystemEnvironment(string $key): ?string + { + // Priority: $_ENV > $_SERVER > getenv() + // $_ENV and $_SERVER may contain dynamically set vars in PHP-FPM + + if (isset($_ENV[$key]) && is_string($_ENV[$key])) { + return $_ENV[$key]; + } + + if (isset($_SERVER[$key]) && is_string($_SERVER[$key]) && !str_starts_with($key, 'HTTP_')) { + return $_SERVER[$key]; + } + + $value = getenv($key); + if ($value !== false && is_string($value)) { + return $value; + } + + return null; + } + public function getRequired(EnvKey|string $key): mixed { $key = $this->keyToString($key); @@ -115,11 +150,7 @@ final readonly class Environment */ public function all(): array { - return array_merge( - #$_ENV, - #$_SERVER, - $this->variables - ); + return $this->variables; } /**