mirror of
https://github.com/symfony/dotenv.git
synced 2026-03-23 23:52:14 +01:00
Merge branch '6.4' into 7.4
* 6.4: [HttpKernel] Reset router locale to default when finishing main request Only decrement pendingRequests when it's more than zero [Dotenv] Fix self-referencing variables with defaults and env key resolution during deferred expansion Improve Bulgarian translations in validators.bg.xlf [Cache] Fix ChainAdapter ignoring item expiry when propagating to earlier adapters [Form] Fix typed property initialization in ValidatorExtension [Messenger] Fix duplicate pending messages in Redis transport with batch handlers Fix deprecation notices for "@method" annotations when implementing interfaces directly
This commit is contained in:
87
Dotenv.php
87
Dotenv.php
@@ -113,11 +113,16 @@ final class Dotenv
|
||||
|
||||
if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) {
|
||||
$this->populate([$k => $env = $defaultEnv], $overrideExistingVars);
|
||||
} elseif (str_contains($env, '$') || str_contains($env, "\x00") || str_contains($env, '\\')) {
|
||||
$env = $this->resolveEnvKey($env, $k);
|
||||
}
|
||||
|
||||
if (!\in_array($env, $testEnvs, true) && is_file($p = "$path.local")) {
|
||||
$this->doLoad($overrideExistingVars, [$p]);
|
||||
$env = $_SERVER[$k] ?? $_ENV[$k] ?? $env;
|
||||
if (str_contains($env, '$') || str_contains($env, "\x00") || str_contains($env, '\\')) {
|
||||
$env = $this->resolveEnvKey($env, $k);
|
||||
}
|
||||
}
|
||||
|
||||
if ('local' === $env) {
|
||||
@@ -642,6 +647,50 @@ final class Dotenv
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly resolves a raw env key value so that loadEnv() can determine
|
||||
* which additional .env files to load before full deferred resolution.
|
||||
*/
|
||||
private function resolveEnvKey(string $value, string $name): string
|
||||
{
|
||||
$loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? ''));
|
||||
unset($loadedVars['']);
|
||||
|
||||
// Save and clear own value so self-referencing defaults work
|
||||
$envBackup = $_ENV[$name] ?? null;
|
||||
$serverBackup = $_SERVER[$name] ?? null;
|
||||
unset($_ENV[$name], $_SERVER[$name]);
|
||||
if ($this->usePutenv) {
|
||||
$getenvBackup = (string) getenv($name);
|
||||
putenv($name);
|
||||
}
|
||||
|
||||
$this->values = [];
|
||||
$this->path = '';
|
||||
$this->data = '';
|
||||
$this->lineno = 0;
|
||||
$this->cursor = 0;
|
||||
$this->end = 0;
|
||||
|
||||
$resolved = $this->resolveCommands($value, $loadedVars);
|
||||
$resolved = $this->resolveVariables($resolved, $loadedVars);
|
||||
$resolved = str_replace(["\x00", '\\\\'], ['$', '\\'], $resolved);
|
||||
|
||||
if (null !== $envBackup) {
|
||||
$_ENV[$name] = $envBackup;
|
||||
}
|
||||
if (null !== $serverBackup) {
|
||||
$_SERVER[$name] = $serverBackup;
|
||||
}
|
||||
if ($this->usePutenv) {
|
||||
putenv("$name=$getenvBackup");
|
||||
}
|
||||
|
||||
$this->values = [];
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
private function resolveLoadedVars(): void
|
||||
{
|
||||
$loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? ''));
|
||||
@@ -654,6 +703,20 @@ final class Dotenv
|
||||
$this->cursor = 0;
|
||||
$this->end = 0;
|
||||
|
||||
// Detect variables that were originally defined as self-referencing
|
||||
// (e.g. MY_VAR="${MY_VAR:-default}") so their own raw value is hidden
|
||||
// during resolution, allowing the default to trigger correctly.
|
||||
$selfReferencingVars = [];
|
||||
foreach ($loadedVars as $name => $_) {
|
||||
if ('SYMFONY_DOTENV_VARS' === $name) {
|
||||
continue;
|
||||
}
|
||||
$value = $_ENV[$name] ?? '';
|
||||
if (str_contains($value, '$') && preg_match('/\$\{?'.preg_quote($name, '/').'(?![A-Za-z0-9_])/', $value)) {
|
||||
$selfReferencingVars[$name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ($pass = 0; $pass < 5; ++$pass) {
|
||||
$resolved = [];
|
||||
foreach ($loadedVars as $name => $_) {
|
||||
@@ -663,8 +726,32 @@ final class Dotenv
|
||||
if (!str_contains($value = $_ENV[$name] ?? '', '$')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($selfReferencingVars[$name])) {
|
||||
$envBackup = $_ENV[$name] ?? null;
|
||||
$serverBackup = $_SERVER[$name] ?? null;
|
||||
unset($_ENV[$name], $_SERVER[$name]);
|
||||
if ($this->usePutenv) {
|
||||
$getenvBackup = $this->usePutenv ? (string) getenv($name) : null;
|
||||
putenv($name);
|
||||
}
|
||||
}
|
||||
|
||||
$resolvedValue = $this->resolveCommands($value, $loadedVars);
|
||||
$resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
|
||||
|
||||
if (isset($selfReferencingVars[$name])) {
|
||||
if (null !== $envBackup) {
|
||||
$_ENV[$name] = $envBackup;
|
||||
}
|
||||
if (null !== $serverBackup) {
|
||||
$_SERVER[$name] = $serverBackup;
|
||||
}
|
||||
if ($this->usePutenv) {
|
||||
putenv("$name=$getenvBackup");
|
||||
}
|
||||
}
|
||||
|
||||
if ($value !== $resolvedValue) {
|
||||
$resolved[$name] = $resolvedValue;
|
||||
}
|
||||
|
||||
@@ -542,6 +542,90 @@ class DotenvTest extends TestCase
|
||||
rmdir($tmpdir);
|
||||
}
|
||||
|
||||
public function testLoadEnvSelfReferencingVariableWithDefault()
|
||||
{
|
||||
$resetContext = static function (): void {
|
||||
unset($_ENV['SYMFONY_DOTENV_VARS'], $_ENV['MY_VAR'], $_ENV['TEST_APP_ENV']);
|
||||
unset($_SERVER['SYMFONY_DOTENV_VARS'], $_SERVER['MY_VAR'], $_SERVER['TEST_APP_ENV']);
|
||||
putenv('SYMFONY_DOTENV_VARS');
|
||||
putenv('MY_VAR');
|
||||
putenv('TEST_APP_ENV');
|
||||
};
|
||||
|
||||
@mkdir($tmpdir = sys_get_temp_dir().'/dotenv');
|
||||
$path = tempnam($tmpdir, 'sf-');
|
||||
|
||||
// Self-referencing variable with default value
|
||||
file_put_contents($path, 'MY_VAR="${MY_VAR:-default_value}"');
|
||||
|
||||
$resetContext();
|
||||
(new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
|
||||
|
||||
$this->assertSame('default_value', getenv('MY_VAR'));
|
||||
|
||||
// When host env is set, it should take precedence
|
||||
$resetContext();
|
||||
putenv('MY_VAR=host_value');
|
||||
$_ENV['MY_VAR'] = 'host_value';
|
||||
(new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
|
||||
|
||||
$this->assertSame('host_value', getenv('MY_VAR'));
|
||||
|
||||
// Self-referencing variable with := (assign default)
|
||||
file_put_contents($path, 'MY_VAR="${MY_VAR:=fallback}"');
|
||||
|
||||
$resetContext();
|
||||
(new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
|
||||
|
||||
$this->assertSame('fallback', getenv('MY_VAR'));
|
||||
|
||||
$resetContext();
|
||||
putenv('MY_VAR');
|
||||
unlink($path);
|
||||
@rmdir($tmpdir);
|
||||
}
|
||||
|
||||
public function testLoadEnvSelfReferencingEnvKeyControlsFileLoading()
|
||||
{
|
||||
$resetContext = static function (): void {
|
||||
unset($_ENV['SYMFONY_DOTENV_VARS'], $_ENV['TEST_APP_ENV'], $_ENV['FOO']);
|
||||
unset($_SERVER['SYMFONY_DOTENV_VARS'], $_SERVER['TEST_APP_ENV'], $_SERVER['FOO']);
|
||||
putenv('SYMFONY_DOTENV_VARS');
|
||||
putenv('TEST_APP_ENV');
|
||||
putenv('FOO');
|
||||
};
|
||||
|
||||
@mkdir($tmpdir = sys_get_temp_dir().'/dotenv');
|
||||
$path = tempnam($tmpdir, 'sf-');
|
||||
|
||||
// APP_ENV with self-referencing default must control which .env files are loaded
|
||||
file_put_contents($path, 'TEST_APP_ENV="${TEST_APP_ENV:-dev}"'."\nFOO=bar");
|
||||
file_put_contents("$path.dev", 'FOO=devbar');
|
||||
|
||||
$resetContext();
|
||||
(new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
|
||||
|
||||
$this->assertSame('dev', getenv('TEST_APP_ENV'));
|
||||
$this->assertSame('devbar', getenv('FOO'));
|
||||
|
||||
// Host env should override the default and control file loading
|
||||
$resetContext();
|
||||
file_put_contents("$path.prod", 'FOO=prodbar');
|
||||
putenv('TEST_APP_ENV=prod');
|
||||
$_ENV['TEST_APP_ENV'] = 'prod';
|
||||
(new Dotenv())->usePutenv()->loadEnv($path, 'TEST_APP_ENV');
|
||||
|
||||
$this->assertSame('prod', getenv('TEST_APP_ENV'));
|
||||
$this->assertSame('prodbar', getenv('FOO'));
|
||||
|
||||
$resetContext();
|
||||
putenv('TEST_APP_ENV');
|
||||
@unlink("$path.dev");
|
||||
@unlink("$path.prod");
|
||||
unlink($path);
|
||||
@rmdir($tmpdir);
|
||||
}
|
||||
|
||||
public function testLoadEnvThrowsOnCircularVariableReferences()
|
||||
{
|
||||
$resetContext = static function (): void {
|
||||
|
||||
Reference in New Issue
Block a user