$variables */ public function __construct( private array $variables = [], private DockerSecretsResolver $secretsResolver = new DockerSecretsResolver() ) { } public function get(EnvKey|string $key, mixed $default = null): mixed { $key = $this->keyToString($key); // 1. Check if direct env var exists in internal array and is not empty // Empty strings are treated as "not set" to allow Docker Secrets resolution if (isset($this->variables[$key])) { $value = $this->variables[$key]; // If value is not empty, return it (non-empty values take precedence) if ($value !== '' && $value !== null) { return $value; } // If value is empty, continue to check Docker Secrets as fallback } // 2. Docker Secrets support: Check for *_FILE pattern // This allows Docker Secrets to override empty values $secretValue = $this->secretsResolver->resolve($key, $this->variables); if ($secretValue !== null) { return $secretValue; } // 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 && $systemValue !== '') { return $systemValue; } // 4. If internal variable was set (even if empty), return it (to distinguish between "not set" and "empty") // This preserves the original behavior: if variable is explicitly set to empty string, return it if (isset($this->variables[$key])) { return $this->variables[$key]; } // 5. 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); $value = $this->get($key); if ($value === null) { throw new RequiredEnvironmentVariableException($key); } return $value; } public function getInt(EnvKey|string $key, int $default = 0): int { $key = $this->keyToString($key); return (int) $this->get($key, $default); } public function getFloat(EnvKey|string $key, float $default = 0.0): float { $key = $this->keyToString($key); return (float) $this->get($key, $default); } public function getBool(EnvKey|string $key, bool $default = false): bool { $key = $this->keyToString($key); $value = $this->get($key, $default); if (is_string($value)) { return match (strtolower($value)) { 'true', '1', 'yes', 'on' => true, 'false', '0', 'no', 'off' => false, default => $default }; } return (bool) $value; } public function getString(EnvKey|string $key, string $default = ''): string { $key = $this->keyToString($key); return (string) $this->get($key, $default); } /** @param class-string $enumClass */ public function getEnum(EnvKey|string $key, string $enumClass, BackedEnum $default): BackedEnum { $key = $this->keyToString($key); if (! $default instanceof $enumClass) { throw new \InvalidArgumentException('Default value must be an instance of the enum class'); } $value = $this->get($key); if ($value === null) { return $default; } return $enumClass::tryFrom($value) ?? $default; } public function has(EnvKey|string $key): bool { $key = $this->keyToString($key); return $this->get($key) !== null; } /** * @return array */ public function all(bool $sorted = false): array { // Merge internal variables with system environment variables // This ensures all available environment variables are returned, // including those that became available after Environment initialization // (e.g., set by PHP-FPM/FastCGI during request processing) $systemVariables = $this->getSystemEnvironment(); // Merge: internal variables take precedence over system variables // This ensures variables loaded from .env files or set during initialization // take precedence over system environment variables $all = array_merge($systemVariables, $this->variables); // Resolve Docker Secrets for variables that are empty or not set // This ensures that variables like DB_PASSWORD are resolved from their *_FILE counterparts $resolved = []; foreach ($all as $key => $value) { // If variable is empty or not set, check for Docker Secret if (empty($value) || $value === '' || $value === null) { $secretValue = $this->secretsResolver->resolve($key, $all); if ($secretValue !== null) { $resolved[$key] = $secretValue; continue; } } // Include non-empty values and *_FILE variables $resolved[$key] = $value; } if ($sorted) { ksort($resolved); } return $resolved; } /** * Get all system environment variables dynamically * * @return array */ private function getSystemEnvironment(): array { $variables = []; // Load from $_ENV (contains dynamically set vars in PHP-FPM) foreach ($_ENV as $key => $value) { if (is_string($key) && is_string($value) && !str_starts_with($key, 'HTTP_')) { $variables[$key] = $value; } } // Load from $_SERVER (may contain additional vars from web server) 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; } } // Load from getenv() as fallback $allEnvVars = getenv(); if ($allEnvVars !== false) { foreach ($allEnvVars as $key => $value) { if (!isset($variables[$key])) { $variables[$key] = $value; } } } return $variables; } /** * Factory method für .env file loading */ public static function fromFile(FilePath|string $envPath): self { $parser = new EnvFileParser(); $variables = $parser->parse($envPath); return new self($variables); } public function keyToString(EnvKey|string $key): string { if (is_string($key)) { return $key; } return $key->value; } /** * Für Tests */ public function withVariable(string $key, mixed $value): self { $variables = $this->variables; $variables[$key] = $value; return new self($variables); } /** * @param array $variables */ public function withVariables(array $variables): self { return new self(array_merge($this->variables, $variables)); } }