From 774a78c8de45351c7f10c002090240bcecdee8b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 21 Sep 2018 08:27:43 +0200 Subject: [PATCH] [DotEnv] Add a new loadForEnv() method mimicking Ruby's dotenv behavior --- src/Symfony/Component/Dotenv/Dotenv.php | 57 +++++++++++++----- .../Component/Dotenv/Tests/DotenvTest.php | 58 +++++++++++++++++++ 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 827bbc8503024..2a1d3551d4a48 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -21,6 +21,7 @@ * Manages .env files. * * @author Fabien Potencier + * @author Kévin Dunglas */ final class Dotenv { @@ -39,29 +40,57 @@ final class Dotenv /** * Loads one or several .env files. * - * @param string $path A file to load - * @param ...string $paths A list of additional files to load + * @param string $path A file to load + * @param ...string $extraPaths A list of additional files to load * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable */ - public function load(string $path, string ...$paths): void + public function load(string $path, string ...$extraPaths): void { - $this->doLoad(false, $path, $paths); + $this->doLoad(false, false, \func_get_args()); + } + + /** + * Loads one or several .env and the corresponding env.$env, env.local and env.$env.local files if they exist. + * + * .env.local is always ignored in test env because tests should produce the same results for everyone. + * + * @param string $path A file to load + * @param ...string $extraPaths A list of additional files to load + * + * @throws FormatException when a file has a syntax error + * @throws PathException when a file does not exist or is not readable + * + * @see https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use + */ + public function loadForEnv(string $env, string $path, string ...$extraPaths): void + { + $paths = \func_get_args(); + for ($i = 1; $i < \func_num_args(); ++$i) { + $path = $paths[$i]; + $pathList = array($path, "$path.$env"); + if ('test' !== $env) { + $pathList[] = "$path.local"; + } + $pathList[] = "$path.$env.local"; + + $this->doLoad(false, true, $pathList); + } } /** * Loads one or several .env files and enables override existing vars. * - * @param string $path A file to load - * @param ...string $paths A list of additional files to load + * @param string $path A file to load + * @param ...string $extraPaths A list of additional files to load * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable */ - public function overload(string $path, string ...$paths): void + public function overload(string $path, string ...$extraPaths): void { - $this->doLoad(true, $path, $paths); + $this->doLoad(true, false, \func_get_args()); } /** @@ -405,16 +434,14 @@ private function createFormatException($message) return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor)); } - private function doLoad(bool $overrideExistingVars, string $path, array $paths): void + private function doLoad(bool $overrideExistingVars, bool $ignoreMissingExtraPaths, array $paths): void { - array_unshift($paths, $path); - - foreach ($paths as $path) { - if (!is_readable($path) || is_dir($path)) { + foreach ($paths as $i => $path) { + if (is_readable($path) && !is_dir($path)) { + $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars); + } elseif (!$ignoreMissingExtraPaths || 0 === $i) { throw new PathException($path); } - - $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars); } } } diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php index bc0b883e056b3..2e9331772aa1a 100644 --- a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php +++ b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php @@ -186,6 +186,64 @@ public function testLoad() $this->assertSame('BAZ', $bar); } + public function testLoadForEnv() + { + unset($_ENV['FOO']); + unset($_ENV['BAR']); + unset($_SERVER['FOO']); + unset($_SERVER['BAR']); + putenv('FOO'); + putenv('BAR'); + + @mkdir($tmpdir = sys_get_temp_dir().'/dotenv'); + + $path1 = tempnam($tmpdir, 'sf-'); + $path2 = tempnam($tmpdir, 'sf-'); + + file_put_contents($path1, 'FOO=BAR'); + file_put_contents($path2, 'BAR=BAZ'); + + // .env + + (new DotEnv())->loadForEnv('dev', $path1, $path2); + + $this->assertSame('BAR', getenv('FOO')); + $this->assertSame('BAZ', getenv('BAR')); + + // .env.dev + + file_put_contents("$path1.dev", 'FOO=devBAR'); + (new DotEnv())->loadForEnv('dev', $path1, $path2); + $this->assertSame('devBAR', getenv('FOO')); + + // .env.local + + file_put_contents("$path1.local", 'FOO=localBAR'); + (new DotEnv())->loadForEnv('dev', $path1, $path2); + $this->assertSame('localBAR', getenv('FOO')); + + // special case for test + + file_put_contents("$path1.local", 'FOO=testBAR'); + (new DotEnv())->loadForEnv('test', $path1, $path2); + $this->assertSame('BAR', getenv('FOO')); + + // .env.dev.local + + file_put_contents("$path1.dev.local", 'FOO=devlocalBAR'); + (new DotEnv())->loadForEnv('dev', $path1, $path2); + $this->assertSame('devlocalBAR', getenv('FOO')); + + putenv('FOO'); + putenv('BAR'); + unlink($path1); + unlink("$path1.dev"); + unlink("$path1.local"); + unlink("$path1.dev.local"); + unlink($path2); + rmdir($tmpdir); + } + public function testOverload() { unset($_ENV['FOO']);