Skip to content

Commit

Permalink
bug #32943 [Dotenv] search variable values in ENV first then env file…
Browse files Browse the repository at this point in the history
… (soufianZantar)

This PR was merged into the 3.4 branch.

Discussion
----------

[Dotenv] search variable values in ENV first then env file

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #32595
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

<!--
Replace this notice by a short README for your feature/bugfix. This will help people
understand your PR and can be used as a start for the documentation.

Additionally (see https://symfony.com/roadmap):
 - Bug fixes must be submitted against the lowest maintained branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too).
 - Features and deprecations must be submitted against branch 4.4.
 - Legacy code removals go to the master branch.
-->

I think we must searhing the value of variables in $_ENV before the .env file to fix this issues.
before this fix Parse method will return the value in .env file and not the value passed in this command `composer dump-env prod `.

**the issue:**

In my .env file, I have a variable TEST that depends on the APP_ENV variable like this:

```
# .env file

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=2eb810c79fba0dd5c029a2fa53bfdb51
###< symfony/framework-bundle ###

TEST=foo_${APP_ENV}

```
I run composer dump-env dev command to generate my .env.locale.php, everything works fine, the value of my variable TEST is correct.

```
// .env.locale.php

return array (
  'APP_ENV' => 'dev',
  'TEST' => 'foo_dev',
  'APP_SECRET' => '2eb810c79fba0dd5c029a2fa53bfdb51',
);
```
Then I run the same command with prod environment (composer dump-env prod), the value of TEST is wrong (it is same as for dev)

```
// .env.locale.php

return array (
  'APP_ENV' => 'prod',
  'TEST' => 'foo_dev',
  'APP_SECRET' => '2eb810c79fba0dd5c029a2fa53bfdb51',
);
```

Commits
-------

3018041 [Dotenv] search variable values in ENV first then env file
  • Loading branch information
nicolas-grekas committed Oct 11, 2019
2 parents 49ad46e + 3018041 commit fefb2ff
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 14 deletions.
38 changes: 25 additions & 13 deletions src/Symfony/Component/Dotenv/Dotenv.php
Expand Up @@ -183,6 +183,8 @@ private function lexValue()
throw $this->createFormatException('Whitespace are not supported before the value');
}

$loadedVars = array_flip(explode(',', isset($_SERVER['SYMFONY_DOTENV_VARS']) ? $_SERVER['SYMFONY_DOTENV_VARS'] : (isset($_ENV['SYMFONY_DOTENV_VARS']) ? $_ENV['SYMFONY_DOTENV_VARS'] : '')));
unset($loadedVars['']);
$v = '';

do {
Expand Down Expand Up @@ -224,8 +226,8 @@ private function lexValue()
++$this->cursor;
$value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value);
$resolvedValue = $value;
$resolvedValue = $this->resolveVariables($resolvedValue);
$resolvedValue = $this->resolveCommands($resolvedValue);
$resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
$resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars);
$resolvedValue = str_replace('\\\\', '\\', $resolvedValue);
$v .= $resolvedValue;
} else {
Expand All @@ -247,8 +249,8 @@ private function lexValue()
}
$value = rtrim($value);
$resolvedValue = $value;
$resolvedValue = $this->resolveVariables($resolvedValue);
$resolvedValue = $this->resolveCommands($resolvedValue);
$resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
$resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars);
$resolvedValue = str_replace('\\\\', '\\', $resolvedValue);

if ($resolvedValue === $value && preg_match('/\s+/', $value)) {
Expand Down Expand Up @@ -301,7 +303,7 @@ private function skipEmptyLines()
}
}

private function resolveCommands($value)
private function resolveCommands($value, $loadedVars)
{
if (false === strpos($value, '$')) {
return $value;
Expand All @@ -317,7 +319,7 @@ private function resolveCommands($value)
)
/x';

return preg_replace_callback($regex, function ($matches) {
return preg_replace_callback($regex, function ($matches) use ($loadedVars) {
if ('\\' === $matches[1]) {
return substr($matches[0], 1);
}
Expand All @@ -332,7 +334,15 @@ private function resolveCommands($value)

$process = new Process('echo '.$matches[0]);
$process->inheritEnvironmentVariables(true);
$process->setEnv($this->values);

$env = [];
foreach ($this->values as $name => $value) {
if (isset($loadedVars[$name]) || (!isset($_ENV[$name]) && !(isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')))) {
$env[$name] = $value;
}
}
$process->setEnv($env);

try {
$process->mustRun();
} catch (ProcessException $e) {
Expand All @@ -343,7 +353,7 @@ private function resolveCommands($value)
}, $value);
}

private function resolveVariables($value)
private function resolveVariables($value, array $loadedVars)
{
if (false === strpos($value, '$')) {
return $value;
Expand All @@ -359,7 +369,7 @@ private function resolveVariables($value)
(?P<closing_brace>\})? # optional closing brace
/x';

$value = preg_replace_callback($regex, function ($matches) {
$value = preg_replace_callback($regex, function ($matches) use ($loadedVars) {
// odd number of backslashes means the $ character is escaped
if (1 === \strlen($matches['backslashes']) % 2) {
return substr($matches[0], 1);
Expand All @@ -375,14 +385,16 @@ private function resolveVariables($value)
}

$name = $matches['name'];
if (isset($this->values[$name])) {
if (isset($loadedVars[$name]) && isset($this->values[$name])) {
$value = $this->values[$name];
} elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
$value = $_SERVER[$name];
} elseif (isset($_ENV[$name])) {
$value = $_ENV[$name];
} elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
$value = $_SERVER[$name];
} elseif (isset($this->values[$name])) {
$value = $this->values[$name];
} else {
$value = (string) getenv($name);
$value = '';
}

if (!$matches['opening_brace'] && isset($matches['closing_brace'])) {
Expand Down
17 changes: 17 additions & 0 deletions src/Symfony/Component/Dotenv/Tests/DotenvTest.php
Expand Up @@ -63,6 +63,7 @@ public function testParse($data, $expected)
public function getEnvData()
{
putenv('LOCAL=local');
$_ENV['LOCAL'] = 'local';
$_ENV['REMOTE'] = 'remote';

$tests = [
Expand Down Expand Up @@ -295,4 +296,20 @@ public function testOverridingEnvVarsWithNamesMemorizedInSpecialVar()
$this->assertSame('baz1', getenv('BAZ'));
$this->assertSame('/var/www', getenv('DOCUMENT_ROOT'));
}

public function testGetVariablesValueFromEnvFirst()
{
$_ENV['APP_ENV'] = 'prod';
$dotenv = new Dotenv(true);

$test = "APP_ENV=dev\nTEST1=foo1_\${APP_ENV}";
$values = $dotenv->parse($test);
$this->assertSame('foo1_prod', $values['TEST1']);

if ('\\' !== \DIRECTORY_SEPARATOR) {
$test = "APP_ENV=dev\nTEST2=foo2_\$(php -r 'echo \$_SERVER[\"APP_ENV\"];')";
$values = $dotenv->parse($test);
$this->assertSame('foo2_prod', $values['TEST2']);
}
}
}
2 changes: 1 addition & 1 deletion src/Symfony/Component/Dotenv/composer.json
Expand Up @@ -19,7 +19,7 @@
"php": "^5.5.9|>=7.0.8"
},
"require-dev": {
"symfony/process": "~3.2|~4.0"
"symfony/process": "^3.4.2|^4.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Dotenv\\": "" },
Expand Down

0 comments on commit fefb2ff

Please sign in to comment.