diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index b495d60cba999..e19f2cbbcdff2 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -80,7 +80,13 @@ public function usePutenv(bool $usePutenv = true): static */ public function load(string $path, string ...$extraPaths): void { - $this->doLoad(false, \func_get_args()); + $needsAutoValueResolution = 1 === \func_num_args(); + + $this->doLoad(false, $needsAutoValueResolution, \func_get_args()); + + if (!$needsAutoValueResolution) { + $this->resolveAllVariables(); + } } /** @@ -99,12 +105,16 @@ public function load(string $path, string ...$extraPaths): void */ public function loadEnv(string $path, string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void { + // FIXME - Keep 'false' for loadEnv() or try to know if we will have multiple files to load? + // -> The environment is grabbed on the first doLoad() call so we cannot check conditions (except by loading the files multiple times..) + $needsAutoValueResolution = false; + $k = $envKey ?? $this->envKey; if (is_file($path) || !is_file($p = "$path.dist")) { - $this->doLoad($overrideExistingVars, [$path]); + $this->doLoad($overrideExistingVars, $needsAutoValueResolution, [$path]); } else { - $this->doLoad($overrideExistingVars, [$p]); + $this->doLoad($overrideExistingVars, $needsAutoValueResolution, [$p]); } if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) { @@ -112,20 +122,28 @@ public function loadEnv(string $path, string $envKey = null, string $defaultEnv } if (!\in_array($env, $testEnvs, true) && is_file($p = "$path.local")) { - $this->doLoad($overrideExistingVars, [$p]); + $this->doLoad($overrideExistingVars, $needsAutoValueResolution, [$p]); $env = $_SERVER[$k] ?? $_ENV[$k] ?? $env; } if ('local' === $env) { + if (!$needsAutoValueResolution) { + $this->resolveAllVariables(); + } + return; } if (is_file($p = "$path.$env")) { - $this->doLoad($overrideExistingVars, [$p]); + $this->doLoad($overrideExistingVars, $needsAutoValueResolution, [$p]); } if (is_file($p = "$path.$env.local")) { - $this->doLoad($overrideExistingVars, [$p]); + $this->doLoad($overrideExistingVars, $needsAutoValueResolution, [$p]); + } + + if (!$needsAutoValueResolution) { + $this->resolveAllVariables(); } } @@ -166,7 +184,13 @@ public function bootEnv(string $path, string $defaultEnv = 'dev', array $testEnv */ public function overload(string $path, string ...$extraPaths): void { - $this->doLoad(true, \func_get_args()); + $needsAutoValueResolution = 1 === \func_num_args(); + + $this->doLoad(true, $needsAutoValueResolution, \func_get_args()); + + if (!$needsAutoValueResolution) { + $this->resolveAllVariables(); + } } /** @@ -219,12 +243,13 @@ public function populate(array $values, bool $overrideExistingVars = false): voi /** * Parses the contents of an .env file. * - * @param string $data The data to be parsed - * @param string $path The original file name where data where stored (used for more meaningful error messages) + * @param string $data The data to be parsed + * @param string $path The original file name where data where stored (used for more meaningful error messages) + * @param bool $needsValueResolution true when the value resolution needs to be done automatically * * @throws FormatException when a file has a syntax error */ - public function parse(string $data, string $path = '.env'): array + public function parse(string $data, string $path = '.env', bool $needsValueResolution = true): array { $this->path = $path; $this->data = str_replace(["\r\n", "\r"], "\n", $data); @@ -245,7 +270,7 @@ public function parse(string $data, string $path = '.env'): array break; case self::STATE_VALUE: - $this->values[$name] = $this->lexValue(); + $this->values[$name] = $this->lexValue($needsValueResolution); $state = self::STATE_VARNAME; break; } @@ -291,7 +316,7 @@ private function lexVarname(): string return $matches[2]; } - private function lexValue(): string + private function lexValue(bool $needsValueResolution): string { if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) { $this->moveCursor($matches[0]); @@ -340,7 +365,11 @@ private function lexValue(): string ++$this->cursor; $value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value); $resolvedValue = $value; - $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); + + if ($needsValueResolution) { + $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); + } + $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); $v .= $resolvedValue; @@ -363,7 +392,11 @@ private function lexValue(): string } $value = rtrim($value); $resolvedValue = $value; - $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); + + if ($needsValueResolution) { + $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); + } + $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); @@ -545,14 +578,35 @@ private function createFormatException(string $message): FormatException return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor)); } - private function doLoad(bool $overrideExistingVars, array $paths): void + private function doLoad(bool $overrideExistingVars, bool $needsValueResolution, array $paths): void { foreach ($paths as $path) { if (!is_readable($path) || is_dir($path)) { throw new PathException($path); } - $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars); + $this->populate($this->parse(file_get_contents($path), $path, $needsValueResolution), $overrideExistingVars); } } + + private function resolveAllVariables(): void + { + $resolvedVars = []; + $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '')); + unset($loadedVars['']); + + foreach ($_ENV as $name => $value) { + if ('SYMFONY_DOTENV_VARS' === $name) { + continue; + } + + $resolvedValue = $this->resolveVariables($value, $loadedVars); + + if ($resolvedValue !== $value) { + $resolvedVars[$name] = $resolvedValue; + } + } + + $this->populate($resolvedVars, true); + } } diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php index 47c9bfba137ac..2076a02231413 100644 --- a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php +++ b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php @@ -224,6 +224,53 @@ public function testLoad() $this->assertSame('BAZ', $bar); } + public function testLoadDynamicVar() + { + unset($_ENV['VAR_STATIC1']); + unset($_ENV['VAR_STATIC2']); + unset($_ENV['VAR_DYNAMIC1']); + unset($_ENV['VAR_DYNAMIC2']); + unset($_ENV['VAR_DYNAMIC3']); + unset($_ENV['VAR_DYNAMIC_FAIL']); + unset($_SERVER['VAR_STATIC1']); + unset($_SERVER['VAR_STATIC2']); + unset($_SERVER['VAR_DYNAMIC1']); + unset($_SERVER['VAR_DYNAMIC2']); + unset($_SERVER['VAR_DYNAMIC3']); + unset($_SERVER['VAR_DYNAMIC_FAIL']); + + @mkdir($tmpdir = sys_get_temp_dir().'/dotenv'); + + $path1 = tempnam($tmpdir, 'sf-'); + $path2 = tempnam($tmpdir, 'sf-'); + $path3 = tempnam($tmpdir, 'sf-'); + + file_put_contents($path1, "VAR_STATIC1=env\nVAR_DYNAMIC1=profile:\${VAR_STATIC1}\nVAR_DYNAMIC_FAIL=\$VAR_DYNAMIC1"); + file_put_contents($path2, 'VAR_STATIC1=local'); + file_put_contents($path3, "VAR_STATIC2=file3\nVAR_DYNAMIC2=\$VAR_STATIC1\nVAR_DYNAMIC3=\$VAR_STATIC2"); + + (new Dotenv())->usePutenv()->load($path1, $path2, $path3); + + $static1 = getenv('VAR_STATIC1'); + $static2 = getenv('VAR_STATIC2'); + $dynamic1 = getenv('VAR_DYNAMIC1'); + $dynamic2 = getenv('VAR_DYNAMIC2'); + $dynamic3 = getenv('VAR_DYNAMIC3'); + $dynamicFail = getenv('VAR_DYNAMIC_FAIL'); + + unlink($path1); + unlink($path2); + unlink($path3); + rmdir($tmpdir); + + $this->assertSame('local', $static1); + $this->assertSame('file3', $static2); + $this->assertSame('profile:local', $dynamic1); + $this->assertSame('local', $dynamic2); + $this->assertSame('file3', $dynamic3); + $this->assertSame('profile:${VAR_STATIC1}', $dynamicFail); + } + public function testLoadEnv() { $resetContext = static function (): void { @@ -350,6 +397,38 @@ public function testLoadEnv() rmdir($tmpdir); } + public function testLoadEnvDynamicVar() + { + unset($_ENV['VAR_STATIC1']); + unset($_ENV['VAR_DYNAMIC1']); + unset($_ENV['VAR_DYNAMIC_FAIL']); + unset($_SERVER['VAR_STATIC1']); + unset($_SERVER['VAR_DYNAMIC1']); + unset($_SERVER['VAR_DYNAMIC_FAIL']); + + @mkdir($tmpdir = sys_get_temp_dir().'/dotenv'); + + $pathEnv = tempnam($tmpdir, 'sf-'); + $pathLocalEnv = "$pathEnv.local"; + + file_put_contents($pathEnv, "VAR_STATIC1=env\nVAR_DYNAMIC1=profile:\${VAR_STATIC1}\nVAR_DYNAMIC_FAIL=\$VAR_DYNAMIC1"); + file_put_contents($pathLocalEnv, 'VAR_STATIC1=local'); + + (new Dotenv())->usePutenv()->loadEnv($pathEnv, 'TEST_APP_ENV'); + + $static1 = getenv('VAR_STATIC1'); + $dynamic1 = getenv('VAR_DYNAMIC1'); + $dynamicFail = getenv('VAR_DYNAMIC_FAIL'); + + unlink($pathEnv); + unlink($pathLocalEnv); + rmdir($tmpdir); + + $this->assertSame('local', $static1); + $this->assertSame('profile:local', $dynamic1); + $this->assertSame('profile:${VAR_STATIC1}', $dynamicFail); + } + public function testOverload() { unset($_ENV['FOO']); @@ -385,6 +464,53 @@ public function testOverload() $this->assertSame('BAZ', $bar); } + public function testOverloadDynamicVar() + { + unset($_ENV['VAR_STATIC1']); + unset($_ENV['VAR_STATIC2']); + unset($_ENV['VAR_DYNAMIC1']); + unset($_ENV['VAR_DYNAMIC2']); + unset($_ENV['VAR_DYNAMIC3']); + unset($_ENV['VAR_DYNAMIC_FAIL']); + unset($_SERVER['VAR_STATIC1']); + unset($_SERVER['VAR_STATIC2']); + unset($_SERVER['VAR_DYNAMIC1']); + unset($_SERVER['VAR_DYNAMIC2']); + unset($_SERVER['VAR_DYNAMIC3']); + unset($_SERVER['VAR_DYNAMIC_FAIL']); + + @mkdir($tmpdir = sys_get_temp_dir().'/dotenv'); + + $path1 = tempnam($tmpdir, 'sf-'); + $path2 = tempnam($tmpdir, 'sf-'); + $path3 = tempnam($tmpdir, 'sf-'); + + file_put_contents($path1, "VAR_STATIC1=env\nVAR_DYNAMIC1=profile:\${VAR_STATIC1}\nVAR_DYNAMIC_FAIL=\$VAR_DYNAMIC1"); + file_put_contents($path2, 'VAR_STATIC1=local'); + file_put_contents($path3, "VAR_STATIC2=file3\nVAR_DYNAMIC2=\$VAR_STATIC1\nVAR_DYNAMIC3=\$VAR_STATIC2"); + + (new Dotenv())->usePutenv()->overload($path1, $path2, $path3); + + $static1 = getenv('VAR_STATIC1'); + $static2 = getenv('VAR_STATIC2'); + $dynamic1 = getenv('VAR_DYNAMIC1'); + $dynamic2 = getenv('VAR_DYNAMIC2'); + $dynamic3 = getenv('VAR_DYNAMIC3'); + $dynamicFail = getenv('VAR_DYNAMIC_FAIL'); + + unlink($path1); + unlink($path2); + unlink($path3); + rmdir($tmpdir); + + $this->assertSame('local', $static1); + $this->assertSame('file3', $static2); + $this->assertSame('profile:local', $dynamic1); + $this->assertSame('local', $dynamic2); + $this->assertSame('file3', $dynamic3); + $this->assertSame('profile:${VAR_STATIC1}', $dynamicFail); + } + public function testLoadDirectory() { $this->expectException(PathException::class); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy