From e7c9e4a841dfd950683be93eec519b4aa0324fea Mon Sep 17 00:00:00 2001 From: Valentin PONS Date: Tue, 13 Dec 2022 20:20:42 +0100 Subject: [PATCH 1/4] Added dynamic multi .env file config --- src/Symfony/Component/Dotenv/Dotenv.php | 86 ++++++++++++++++++++----- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index b495d60cba999..3de98ba774e65 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 ($name === 'SYMFONY_DOTENV_VARS') { + continue; + } + + $resolvedValue = $this->resolveVariables($value, $loadedVars); + + if ($resolvedValue !== $value) { + $resolvedVars[$name] = $resolvedValue; + } } + + $this->populate($resolvedVars, true); } } From 8d9ca1af6f574bb92d5ce74d19279d953689ca03 Mon Sep 17 00:00:00 2001 From: Valentin PONS Date: Wed, 14 Dec 2022 10:36:13 +0100 Subject: [PATCH 2/4] Applied coding standard patch --- src/Symfony/Component/Dotenv/Dotenv.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 3de98ba774e65..e19f2cbbcdff2 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -243,9 +243,9 @@ 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 bool $needsValueResolution true when the value resolution needs to be done automatically + * @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 */ @@ -369,7 +369,7 @@ private function lexValue(bool $needsValueResolution): string if ($needsValueResolution) { $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); } - + $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); $v .= $resolvedValue; @@ -396,7 +396,7 @@ private function lexValue(bool $needsValueResolution): string if ($needsValueResolution) { $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); } - + $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); @@ -596,7 +596,7 @@ private function resolveAllVariables(): void unset($loadedVars['']); foreach ($_ENV as $name => $value) { - if ($name === 'SYMFONY_DOTENV_VARS') { + if ('SYMFONY_DOTENV_VARS' === $name) { continue; } From ee8c198ada5b40e21308bdf21974aaac84311483 Mon Sep 17 00:00:00 2001 From: Valentin PONS Date: Thu, 15 Dec 2022 17:29:45 +0100 Subject: [PATCH 3/4] Added tests --- .../Component/Dotenv/Tests/DotenvTest.php | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php index 47c9bfba137ac..aff8ae5915bc1 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); From bcd3b0ccefdb6515323583d16ff53dc617a33fed Mon Sep 17 00:00:00 2001 From: Valentin PONS Date: Thu, 15 Dec 2022 17:38:20 +0100 Subject: [PATCH 4/4] Applied coding standard patch --- src/Symfony/Component/Dotenv/Tests/DotenvTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php index aff8ae5915bc1..2076a02231413 100644 --- a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php +++ b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php @@ -246,7 +246,7 @@ public function testLoadDynamicVar() $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($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); @@ -412,13 +412,13 @@ public function testLoadEnvDynamicVar() $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"); + 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'); + $dynamicFail = getenv('VAR_DYNAMIC_FAIL'); unlink($pathEnv); unlink($pathLocalEnv); @@ -486,7 +486,7 @@ public function testOverloadDynamicVar() $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($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); 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