From a56b24d056ababc8890e44031eb10e6de03448c1 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 19 Oct 2022 00:56:56 +0200 Subject: [PATCH 1/3] [Dotenv] DebugCommand must respect dotenv_path runtime configuration --- .../Component/Dotenv/Command/DebugCommand.php | 52 +++++++++++++------ .../Dotenv/Tests/Command/DebugCommandTest.php | 22 ++++++++ .../Command/Fixtures/Scenario4/.env.dist | 1 + .../Command/Fixtures/Scenario4/.env.prod | 2 + .../Command/Fixtures/Scenario4/composer.json | 7 +++ .../Fixtures/Scenario4/sub/path/.env.local | 1 + .../Scenario4/sub/path/.env.local.php | 5 ++ 7 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.dist create mode 100644 src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.prod create mode 100644 src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json create mode 100644 src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local create mode 100644 src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local.php diff --git a/src/Symfony/Component/Dotenv/Command/DebugCommand.php b/src/Symfony/Component/Dotenv/Command/DebugCommand.php index 8ceb1fd484845..f00603de930ab 100644 --- a/src/Symfony/Component/Dotenv/Command/DebugCommand.php +++ b/src/Symfony/Component/Dotenv/Command/DebugCommand.php @@ -49,30 +49,33 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } - $envFiles = $this->getEnvFiles(); - $availableFiles = array_filter($envFiles, function (string $file) { - return is_file($this->getFilePath($file)); + $dotenvDirectory = $this->getDotenvDirectory(); + $envFiles = $this->getEnvFiles($dotenvDirectory); + $availableFiles = array_filter($envFiles, function (string $file) use ($dotenvDirectory) { + return is_file($this->getFilePath($dotenvDirectory, $file)); }); if (\in_array('.env.local.php', $availableFiles, true)) { $io->warning('Due to existing dump file (.env.local.php) all other dotenv files are skipped.'); } - if (is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) { + if (is_file($this->getFilePath($dotenvDirectory, '.env')) && is_file($this->getFilePath($dotenvDirectory, '.env.dist'))) { $io->warning('The file .env.dist gets skipped due to the existence of .env.'); } $io->section('Scanned Files (in descending priority)'); - $io->listing(array_map(static function (string $envFile) use ($availableFiles) { + $io->listing(array_map(static function (string $envFile) use ($availableFiles, $dotenvDirectory) { + $file = (null === $dotenvDirectory ? '' : $dotenvDirectory.\DIRECTORY_SEPARATOR).$envFile; + return \in_array($envFile, $availableFiles, true) - ? sprintf('✓ %s', $envFile) - : sprintf('⨯ %s', $envFile); + ? sprintf('✓ %s', $file) + : sprintf('⨯ %s', $file); }, $envFiles)); $io->section('Variables'); $io->table( array_merge(['Variable', 'Value'], $availableFiles), - $this->getVariables($availableFiles) + $this->getVariables($dotenvDirectory, $availableFiles) ); $io->comment('Note real values might be different between web and CLI.'); @@ -80,7 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - private function getVariables(array $envFiles): array + private function getVariables(?string $dotenvDirectory, array $envFiles): array { $vars = explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ''); sort($vars); @@ -91,7 +94,7 @@ private function getVariables(array $envFiles): array $realValue = $_SERVER[$var]; $varDetails = [$var, $realValue]; foreach ($envFiles as $envFile) { - $values = $fileValues[$envFile] ?? $fileValues[$envFile] = $this->loadValues($envFile); + $values = $fileValues[$envFile] ?? $fileValues[$envFile] = $this->loadValues($dotenvDirectory, $envFile); $varString = $values[$var] ?? 'n/a'; $shortenedVar = $this->getHelper('formatter')->truncate($varString, 30); @@ -104,7 +107,7 @@ private function getVariables(array $envFiles): array return $output; } - private function getEnvFiles(): array + private function getEnvFiles(?string $dotenvDirectory): array { $files = [ '.env.local.php', @@ -116,7 +119,7 @@ private function getEnvFiles(): array $files[] = '.env.local'; } - if (!is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) { + if (!is_file($this->getFilePath($dotenvDirectory, '.env')) && is_file($this->getFilePath($dotenvDirectory, '.env.dist'))) { $files[] = '.env.dist'; } else { $files[] = '.env'; @@ -125,14 +128,14 @@ private function getEnvFiles(): array return $files; } - private function getFilePath(string $file): string + private function getFilePath(?string $dotenvDirectory, string $file): string { - return $this->projectDirectory.\DIRECTORY_SEPARATOR.$file; + return $this->projectDirectory.(null === $dotenvDirectory ? '' : \DIRECTORY_SEPARATOR.$dotenvDirectory).\DIRECTORY_SEPARATOR.$file; } - private function loadValues(string $file): array + private function loadValues(?string $dotenvDirectory, string $file): array { - $filePath = $this->getFilePath($file); + $filePath = $this->getFilePath($dotenvDirectory, $file); if (str_ends_with($filePath, '.php')) { return include $filePath; @@ -140,4 +143,21 @@ private function loadValues(string $file): array return (new Dotenv())->parse(file_get_contents($filePath)); } + + private function getDotenvDirectory(): ?string + { + $projectDir = is_file($projectDir = $this->projectDirectory) ? basename($projectDir) : $projectDir; + + $composerFile = $projectDir.'/composer.json'; + if (is_file($composerFile)) { + $composerContent = json_decode(file_get_contents($composerFile), true); + $dotenvPath = $composerContent['extra']['runtime']['dotenv_path'] ?? null; + + if (null !== $dotenvPath) { + return \dirname($dotenvPath); + } + } + + return null; + } } diff --git a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php index b3b089e4559c9..75de7a745cdaa 100644 --- a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php @@ -122,6 +122,28 @@ public function testScenario2InProdEnv() $this->assertStringContainsString('TEST 1234 1234 1234 0000', $output); } + public function testScenario4InProdEnv() + { + $output = $this->executeCommand(__DIR__.'/Fixtures/Scenario4', 'prod'); + + // Scanned Files + $this->assertStringContainsString('✓ sub/path/.env.local.php', $output); + $this->assertStringContainsString('⨯ sub/path/.env.prod.local', $output); + $this->assertStringContainsString('⨯ sub/path/.env.prod', $output); + $this->assertStringContainsString('✓ sub/path/.env.local', $output); + $this->assertStringContainsString('⨯ sub/path/.env'.\PHP_EOL, $output); + + // Skipped Files + $this->assertStringNotContainsString('.env.dist', $output); + $this->assertStringNotContainsString('.env.dev', $output); + $this->assertStringNotContainsString('.env.test', $output); + + // Variables + $this->assertStringContainsString('Variable Value .env.local.php .env.local', $output); + $this->assertStringContainsString('FOO BaR BaR n/a', $output); + $this->assertStringContainsString('TEST 1234 1234 1111', $output); + } + public function testWarningOnEnvAndEnvDistFile() { $output = $this->executeCommand(__DIR__.'/Fixtures/Scenario3', 'dev'); diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.dist b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.dist new file mode 100644 index 0000000000000..96c193cbcb47d --- /dev/null +++ b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.dist @@ -0,0 +1 @@ +TEST=0000 diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.prod b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.prod new file mode 100644 index 0000000000000..b3116d1f1d898 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/.env.prod @@ -0,0 +1,2 @@ +FOO=BaR +TEST=1234 diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json new file mode 100644 index 0000000000000..399e99bc77b75 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json @@ -0,0 +1,7 @@ +{ + "extra": { + "runtime": { + "dotenv_path": "sub/path/.env" + } + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local new file mode 100644 index 0000000000000..5770c3418bdfe --- /dev/null +++ b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local @@ -0,0 +1 @@ +TEST=1111 diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local.php b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local.php new file mode 100644 index 0000000000000..07744b5876e66 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local.php @@ -0,0 +1,5 @@ + 'BaR', + 'TEST' => '1234', +]; From 2fefa74e8f464152d0b92c7b0655656965dc8a69 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Thu, 17 Nov 2022 22:32:21 +0100 Subject: [PATCH 2/3] Be more flexible with a better approach --- .../Component/Dotenv/Command/DebugCommand.php | 69 +++++++++---------- .../Dotenv/Tests/Command/DebugCommandTest.php | 22 +++--- .../Command/Fixtures/Scenario4/composer.json | 2 +- .../sub/path/{.env.local => custom.env.local} | 0 .../{.env.local.php => custom.env.local.php} | 0 5 files changed, 46 insertions(+), 47 deletions(-) rename src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/{.env.local => custom.env.local} (100%) rename src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/{.env.local.php => custom.env.local.php} (100%) diff --git a/src/Symfony/Component/Dotenv/Command/DebugCommand.php b/src/Symfony/Component/Dotenv/Command/DebugCommand.php index f00603de930ab..ceec309083eaf 100644 --- a/src/Symfony/Component/Dotenv/Command/DebugCommand.php +++ b/src/Symfony/Component/Dotenv/Command/DebugCommand.php @@ -49,33 +49,35 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } - $dotenvDirectory = $this->getDotenvDirectory(); - $envFiles = $this->getEnvFiles($dotenvDirectory); - $availableFiles = array_filter($envFiles, function (string $file) use ($dotenvDirectory) { - return is_file($this->getFilePath($dotenvDirectory, $file)); + $dotenvFile = $this->getDotenvFile(); + $envFiles = $this->getEnvFiles($dotenvFile); + $availableFiles = array_filter($envFiles, function (string $file) { + return is_file($this->getFilePath($file)); }); - if (\in_array('.env.local.php', $availableFiles, true)) { - $io->warning('Due to existing dump file (.env.local.php) all other dotenv files are skipped.'); + $localDotenvFile = sprintf('%s.local.php', $dotenvFile); + if (\in_array($localDotenvFile, $availableFiles, true)) { + $io->warning(sprintf('Due to existing dump file (%s) all other dotenv files are skipped.', $localDotenvFile)); } - if (is_file($this->getFilePath($dotenvDirectory, '.env')) && is_file($this->getFilePath($dotenvDirectory, '.env.dist'))) { - $io->warning('The file .env.dist gets skipped due to the existence of .env.'); + $distDotenvFile = sprintf('%s.dist', $dotenvFile); + if (is_file($this->getFilePath($dotenvFile)) && is_file($this->getFilePath($distDotenvFile))) { + $io->warning(sprintf('The file %s gets skipped due to the existence of %s.', $distDotenvFile, $dotenvFile)); } $io->section('Scanned Files (in descending priority)'); - $io->listing(array_map(static function (string $envFile) use ($availableFiles, $dotenvDirectory) { - $file = (null === $dotenvDirectory ? '' : $dotenvDirectory.\DIRECTORY_SEPARATOR).$envFile; - + $io->listing(array_map(static function (string $envFile) use ($availableFiles) { return \in_array($envFile, $availableFiles, true) - ? sprintf('✓ %s', $file) - : sprintf('⨯ %s', $file); + ? sprintf('✓ %s', $envFile) + : sprintf('⨯ %s', $envFile); }, $envFiles)); $io->section('Variables'); $io->table( - array_merge(['Variable', 'Value'], $availableFiles), - $this->getVariables($dotenvDirectory, $availableFiles) + array_merge(['Variable', 'Value'], array_map(static function (string $availableFile) { + return basename($availableFile); + }, $availableFiles)), + $this->getVariables($availableFiles) ); $io->comment('Note real values might be different between web and CLI.'); @@ -83,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - private function getVariables(?string $dotenvDirectory, array $envFiles): array + private function getVariables(array $envFiles): array { $vars = explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ''); sort($vars); @@ -94,7 +96,7 @@ private function getVariables(?string $dotenvDirectory, array $envFiles): array $realValue = $_SERVER[$var]; $varDetails = [$var, $realValue]; foreach ($envFiles as $envFile) { - $values = $fileValues[$envFile] ?? $fileValues[$envFile] = $this->loadValues($dotenvDirectory, $envFile); + $values = $fileValues[$envFile] ?? $fileValues[$envFile] = $this->loadValues($envFile); $varString = $values[$var] ?? 'n/a'; $shortenedVar = $this->getHelper('formatter')->truncate($varString, 30); @@ -107,35 +109,35 @@ private function getVariables(?string $dotenvDirectory, array $envFiles): array return $output; } - private function getEnvFiles(?string $dotenvDirectory): array + private function getEnvFiles(string $dotenvFile): array { $files = [ - '.env.local.php', - sprintf('.env.%s.local', $this->kernelEnvironment), - sprintf('.env.%s', $this->kernelEnvironment), + sprintf('%s.local.php', $dotenvFile), + sprintf('%s.%s.local', $dotenvFile, $this->kernelEnvironment), + sprintf('%s.%s', $dotenvFile, $this->kernelEnvironment), ]; if ('test' !== $this->kernelEnvironment) { - $files[] = '.env.local'; + $files[] = sprintf('%s.local', $dotenvFile); } - if (!is_file($this->getFilePath($dotenvDirectory, '.env')) && is_file($this->getFilePath($dotenvDirectory, '.env.dist'))) { - $files[] = '.env.dist'; + if (!is_file($this->getFilePath($dotenvFile)) && is_file($this->getFilePath($distDotenvFile = sprintf('%s.dist', $dotenvFile)))) { + $files[] = $distDotenvFile; } else { - $files[] = '.env'; + $files[] = $dotenvFile; } return $files; } - private function getFilePath(?string $dotenvDirectory, string $file): string + private function getFilePath(string $file): string { - return $this->projectDirectory.(null === $dotenvDirectory ? '' : \DIRECTORY_SEPARATOR.$dotenvDirectory).\DIRECTORY_SEPARATOR.$file; + return $this->projectDirectory.\DIRECTORY_SEPARATOR.$file; } - private function loadValues(?string $dotenvDirectory, string $file): array + private function loadValues(string $file): array { - $filePath = $this->getFilePath($dotenvDirectory, $file); + $filePath = $this->getFilePath($file); if (str_ends_with($filePath, '.php')) { return include $filePath; @@ -144,20 +146,17 @@ private function loadValues(?string $dotenvDirectory, string $file): array return (new Dotenv())->parse(file_get_contents($filePath)); } - private function getDotenvDirectory(): ?string + private function getDotenvFile(): string { $projectDir = is_file($projectDir = $this->projectDirectory) ? basename($projectDir) : $projectDir; $composerFile = $projectDir.'/composer.json'; if (is_file($composerFile)) { $composerContent = json_decode(file_get_contents($composerFile), true); - $dotenvPath = $composerContent['extra']['runtime']['dotenv_path'] ?? null; - if (null !== $dotenvPath) { - return \dirname($dotenvPath); - } + return $composerContent['extra']['runtime']['dotenv_path'] ?? '.env'; } - return null; + return '.env'; } } diff --git a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php index 75de7a745cdaa..79bc5f6a5e88b 100644 --- a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php @@ -127,21 +127,21 @@ public function testScenario4InProdEnv() $output = $this->executeCommand(__DIR__.'/Fixtures/Scenario4', 'prod'); // Scanned Files - $this->assertStringContainsString('✓ sub/path/.env.local.php', $output); - $this->assertStringContainsString('⨯ sub/path/.env.prod.local', $output); - $this->assertStringContainsString('⨯ sub/path/.env.prod', $output); - $this->assertStringContainsString('✓ sub/path/.env.local', $output); - $this->assertStringContainsString('⨯ sub/path/.env'.\PHP_EOL, $output); + $this->assertStringContainsString('✓ sub/path/custom.env.local.php', $output); + $this->assertStringContainsString('⨯ sub/path/custom.env.prod.local', $output); + $this->assertStringContainsString('⨯ sub/path/custom.env.prod', $output); + $this->assertStringContainsString('✓ sub/path/custom.env.local', $output); + $this->assertStringContainsString('⨯ sub/path/custom.env'.\PHP_EOL, $output); // Skipped Files - $this->assertStringNotContainsString('.env.dist', $output); - $this->assertStringNotContainsString('.env.dev', $output); - $this->assertStringNotContainsString('.env.test', $output); + $this->assertStringNotContainsString('custom.env.dist', $output); + $this->assertStringNotContainsString('custom.env.dev', $output); + $this->assertStringNotContainsString('custom.env.test', $output); // Variables - $this->assertStringContainsString('Variable Value .env.local.php .env.local', $output); - $this->assertStringContainsString('FOO BaR BaR n/a', $output); - $this->assertStringContainsString('TEST 1234 1234 1111', $output); + $this->assertStringContainsString('Variable Value custom.env.local.php custom.env.local', $output); + $this->assertStringContainsString('FOO BaR BaR n/a', $output); + $this->assertStringContainsString('TEST 1234 1234 1111', $output); } public function testWarningOnEnvAndEnvDistFile() diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json index 399e99bc77b75..3c7ca9f0c8127 100644 --- a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json +++ b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/composer.json @@ -1,7 +1,7 @@ { "extra": { "runtime": { - "dotenv_path": "sub/path/.env" + "dotenv_path": "sub/path/custom.env" } } } \ No newline at end of file diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/custom.env.local similarity index 100% rename from src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local rename to src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/custom.env.local diff --git a/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local.php b/src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/custom.env.local.php similarity index 100% rename from src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/.env.local.php rename to src/Symfony/Component/Dotenv/Tests/Command/Fixtures/Scenario4/sub/path/custom.env.local.php From 20508b35fcd4c2826121344e4a7d607dd24335d4 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 19 Nov 2022 18:13:34 +0100 Subject: [PATCH 3/3] Update src/Symfony/Component/Dotenv/Command/DebugCommand.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jérôme Tamarelle --- src/Symfony/Component/Dotenv/Command/DebugCommand.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Symfony/Component/Dotenv/Command/DebugCommand.php b/src/Symfony/Component/Dotenv/Command/DebugCommand.php index ceec309083eaf..bad818fdc29e8 100644 --- a/src/Symfony/Component/Dotenv/Command/DebugCommand.php +++ b/src/Symfony/Component/Dotenv/Command/DebugCommand.php @@ -74,9 +74,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->section('Variables'); $io->table( - array_merge(['Variable', 'Value'], array_map(static function (string $availableFile) { - return basename($availableFile); - }, $availableFiles)), + array_merge(['Variable', 'Value'], array_map('basename', $availableFiles)), $this->getVariables($availableFiles) );