Skip to content

[Dotenv] Handle dynamic variables in multiple .env files #48636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: 7.4
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added dynamic multi .env file config
  • Loading branch information
valx76 committed Dec 13, 2022
commit e7c9e4a841dfd950683be93eec519b4aa0324fea
86 changes: 70 additions & 16 deletions src/Symfony/Component/Dotenv/Dotenv.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

/**
Expand All @@ -99,33 +105,45 @@ 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) {
$this->populate([$k => $env = $defaultEnv], $overrideExistingVars);
}

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();
}
}

Expand Down Expand Up @@ -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();
}
}

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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;
Expand All @@ -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);

Expand Down Expand Up @@ -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);
}
}
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