From 52b6416ff9539d9d11256de712abf1138ee40a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 17 Nov 2023 23:14:35 +0100 Subject: [PATCH] DotEnv debug command aware of custom dotenv_path Introduce SYMFONY_DOTENV_PATH set by DotEnv class and read by debug:dotenv command to contextualize the debug info with the file that was actually parsed. --- src/Symfony/Component/Dotenv/CHANGELOG.md | 5 ++ .../Component/Dotenv/Command/DebugCommand.php | 50 ++++++++++--------- .../Dotenv/Command/DotenvDumpCommand.php | 1 + src/Symfony/Component/Dotenv/Dotenv.php | 12 +++++ .../Dotenv/Tests/Command/DebugCommandTest.php | 42 +++++++++++++++- 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/src/Symfony/Component/Dotenv/CHANGELOG.md b/src/Symfony/Component/Dotenv/CHANGELOG.md index f3b7b7cf1e467..a587fbac59f2b 100644 --- a/src/Symfony/Component/Dotenv/CHANGELOG.md +++ b/src/Symfony/Component/Dotenv/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `SYMFONY_DOTENV_PATH` variable with the path to the `.env` file loaded by `Dotenv::loadEnv()` or `Dotenv::bootEnv()` + 6.2 --- diff --git a/src/Symfony/Component/Dotenv/Command/DebugCommand.php b/src/Symfony/Component/Dotenv/Command/DebugCommand.php index 1353969c38297..b544530486a46 100644 --- a/src/Symfony/Component/Dotenv/Command/DebugCommand.php +++ b/src/Symfony/Component/Dotenv/Command/DebugCommand.php @@ -70,21 +70,23 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } - $envFiles = $this->getEnvFiles(); - $availableFiles = array_filter($envFiles, fn (string $file) => is_file($this->getFilePath($file))); + $filePath = $_SERVER['SYMFONY_DOTENV_PATH'] ?? $this->projectDirectory.\DIRECTORY_SEPARATOR.'.env'; - if (\in_array('.env.local.php', $availableFiles, true)) { - $io->warning('Due to existing dump file (.env.local.php) all other dotenv files are skipped.'); + $envFiles = $this->getEnvFiles($filePath); + $availableFiles = array_filter($envFiles, fn (string $file) => is_file($file)); + + if (\in_array(sprintf('%s.local.php', $filePath), $availableFiles, true)) { + $io->warning(sprintf('Due to existing dump file (%s.local.php) all other dotenv files are skipped.', $this->getRelativeName($filePath))); } - if (is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) { - $io->warning('The file .env.dist gets skipped due to the existence of .env.'); + if (is_file($filePath) && is_file(sprintf('%s.dist', $filePath))) { + $io->warning(sprintf('The file %s.dist gets skipped due to the existence of %1$s.', $this->getRelativeName($filePath))); } $io->section('Scanned Files (in descending priority)'); - $io->listing(array_map(static fn (string $envFile) => \in_array($envFile, $availableFiles, true) - ? sprintf('✓ %s', $envFile) - : sprintf('⨯ %s', $envFile), $envFiles)); + $io->listing(array_map(fn (string $envFile) => \in_array($envFile, $availableFiles, true) + ? sprintf('✓ %s', $this->getRelativeName($envFile)) + : sprintf('⨯ %s', $this->getRelativeName($envFile)), $envFiles)); $nameFilter = $input->getArgument('filter'); $variables = $this->getVariables($availableFiles, $nameFilter); @@ -93,7 +95,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($variables || null === $nameFilter) { $io->table( - array_merge(['Variable', 'Value'], $availableFiles), + array_merge(['Variable', 'Value'], array_map($this->getRelativeName(...), $availableFiles)), $this->getVariables($availableFiles, $nameFilter) ); @@ -147,36 +149,38 @@ private function getAvailableVars(): array return $vars; } - private function getEnvFiles(): array + private function getEnvFiles(string $filePath): array { $files = [ - '.env.local.php', - sprintf('.env.%s.local', $this->kernelEnvironment), - sprintf('.env.%s', $this->kernelEnvironment), + sprintf('%s.local.php', $filePath), + sprintf('%s.%s.local', $filePath, $this->kernelEnvironment), + sprintf('%s.%s', $filePath, $this->kernelEnvironment), ]; if ('test' !== $this->kernelEnvironment) { - $files[] = '.env.local'; + $files[] = sprintf('%s.local', $filePath); } - if (!is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) { - $files[] = '.env.dist'; + if (!is_file($filePath) && is_file(sprintf('%s.dist', $filePath))) { + $files[] = sprintf('%s.dist', $filePath); } else { - $files[] = '.env'; + $files[] = $filePath; } return $files; } - private function getFilePath(string $file): string + private function getRelativeName(string $filePath): string { - return $this->projectDirectory.\DIRECTORY_SEPARATOR.$file; + if (str_starts_with($filePath, $this->projectDirectory)) { + return substr($filePath, \strlen($this->projectDirectory) + 1); + } + + return basename($filePath); } - private function loadValues(string $file): array + private function loadValues(string $filePath): array { - $filePath = $this->getFilePath($file); - if (str_ends_with($filePath, '.php')) { return include $filePath; } diff --git a/src/Symfony/Component/Dotenv/Command/DotenvDumpCommand.php b/src/Symfony/Component/Dotenv/Command/DotenvDumpCommand.php index 051f4b05a04b3..20f35c9b5b2f3 100644 --- a/src/Symfony/Component/Dotenv/Command/DotenvDumpCommand.php +++ b/src/Symfony/Component/Dotenv/Command/DotenvDumpCommand.php @@ -107,6 +107,7 @@ private function loadEnv(string $dotenvPath, string $env, array $config): array try { $dotenv->loadEnv($dotenvPath, null, 'dev', $testEnvs); unset($_ENV['SYMFONY_DOTENV_VARS']); + unset($_ENV['SYMFONY_DOTENV_PATH']); return $_ENV; } finally { diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 6e693ac28b329..3c27ec6e28410 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -100,6 +100,8 @@ 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 { + $this->populatePath($path); + $k = $envKey ?? $this->envKey; if (is_file($path) || !is_file($p = "$path.dist")) { @@ -144,6 +146,7 @@ public function bootEnv(string $path, string $defaultEnv = 'dev', array $testEnv $k = $this->envKey; if (\is_array($env) && ($overrideExistingVars || !isset($env[$k]) || ($_SERVER[$k] ?? $_ENV[$k] ?? $env[$k]) === $env[$k])) { + $this->populatePath($path); $this->populate($env, $overrideExistingVars); } else { $this->loadEnv($path, $k, $defaultEnv, $testEnvs, $overrideExistingVars); @@ -556,4 +559,13 @@ private function doLoad(bool $overrideExistingVars, array $paths): void $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars); } } + + private function populatePath(string $path): void + { + $_ENV['SYMFONY_DOTENV_PATH'] = $_SERVER['SYMFONY_DOTENV_PATH'] = $path; + + if ($this->usePutenv) { + putenv('SYMFONY_DOTENV_PATH='.$path); + } + } } diff --git a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php index 8bf787336574b..da5a5674034a8 100644 --- a/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Dotenv/Tests/Command/DebugCommandTest.php @@ -124,6 +124,28 @@ public function testScenario2InProdEnv() $this->assertStringContainsString('TEST 1234 1234 1234 0000', $output); } + public function testScenario2WithCustomPath() + { + $output = $this->executeCommand(__DIR__.'/Fixtures', 'prod', [], __DIR__.'/Fixtures/Scenario2/.env'); + + // Scanned Files + $this->assertStringContainsString('✓ Scenario2/.env.local.php', $output); + $this->assertStringContainsString('⨯ Scenario2/.env.prod.local', $output); + $this->assertStringContainsString('✓ Scenario2/.env.prod', $output); + $this->assertStringContainsString('⨯ Scenario2/.env.local', $output); + $this->assertStringContainsString('✓ Scenario2/.env.dist', $output); + + // Skipped Files + $this->assertStringNotContainsString('.env'.\PHP_EOL, $output); + $this->assertStringNotContainsString('.env.dev', $output); + $this->assertStringNotContainsString('.env.test', $output); + + // Variables + $this->assertStringContainsString('Variable Value Scenario2/.env.local.php Scenario2/.env.prod Scenario2/.env.dist', $output); + $this->assertStringContainsString('FOO BaR BaR BaR n/a', $output); + $this->assertStringContainsString('TEST 1234 1234 1234 0000', $output); + } + public function testWarningOnEnvAndEnvDistFile() { $output = $this->executeCommand(__DIR__.'/Fixtures/Scenario3', 'dev'); @@ -132,6 +154,14 @@ public function testWarningOnEnvAndEnvDistFile() $this->assertStringContainsString('[WARNING] The file .env.dist gets skipped', $output); } + public function testWarningOnEnvAndEnvDistFileWithCustomPath() + { + $output = $this->executeCommand(__DIR__.'/Fixtures', 'dev', dotenvPath: __DIR__.'/Fixtures/Scenario3/.env'); + + // Warning + $this->assertStringContainsString('[WARNING] The file Scenario3/.env.dist gets skipped', $output); + } + public function testWarningOnPhpEnvFile() { $output = $this->executeCommand(__DIR__.'/Fixtures/Scenario2', 'prod'); @@ -140,6 +170,14 @@ public function testWarningOnPhpEnvFile() $this->assertStringContainsString('[WARNING] Due to existing dump file (.env.local.php)', $output); } + public function testWarningOnPhpEnvFileWithCustomPath() + { + $output = $this->executeCommand(__DIR__.'/Fixtures', 'prod', dotenvPath: __DIR__.'/Fixtures/Scenario2/.env'); + + // Warning + $this->assertStringContainsString('[WARNING] Due to existing dump file (Scenario2/.env.local.php)', $output); + } + public function testScenario1InDevEnvWithNameFilter() { $output = $this->executeCommand(__DIR__.'/Fixtures/Scenario1', 'dev', ['filter' => 'FoO']); @@ -226,10 +264,10 @@ public function testCompletion() $this->assertSame(['FOO', 'TEST'], $tester->complete([''])); } - private function executeCommand(string $projectDirectory, string $env, array $input = []): string + private function executeCommand(string $projectDirectory, string $env, array $input = [], string $dotenvPath = null): string { $_SERVER['TEST_ENV_KEY'] = $env; - (new Dotenv('TEST_ENV_KEY'))->bootEnv($projectDirectory.'/.env'); + (new Dotenv('TEST_ENV_KEY'))->bootEnv($dotenvPath ?? $projectDirectory.'/.env'); $command = new DebugCommand($env, $projectDirectory); $command->setHelperSet(new HelperSet([new FormatterHelper()]));