From 874cc6728cbff2ea621b45d2532a60ad6d26479b Mon Sep 17 00:00:00 2001 From: Martin Parsiegla Date: Thu, 12 Feb 2026 21:03:27 +0100 Subject: [PATCH 1/2] [Dotenv] Use `APP_RUNTIME_OPTIONS` variable when dumping dotenv --- Command/DebugCommand.php | 51 ++++++++++++++----------- Command/DotenvDumpCommand.php | 26 +++++++++---- Tests/Command/DebugCommandTest.php | 8 ++++ Tests/Command/DotenvDumpCommandTest.php | 38 ++++++++++++++++++ 4 files changed, 92 insertions(+), 31 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 0b6ed33..e279d90 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -42,7 +42,7 @@ final class DebugCommand extends Command public function __construct( private string $kernelEnvironment, - private string $projectDirectory, + private string $projectDir, ) { parent::__construct(); } @@ -77,29 +77,17 @@ final class DebugCommand extends Command return 1; } - if (!$filePath = $_SERVER['SYMFONY_DOTENV_PATH'] ?? null) { - $dotenvPath = $this->projectDirectory; + $dotenvPath = $this->getDotenvPath(); - if (is_file($composerFile = $this->projectDirectory.'/composer.json')) { - $runtimeConfig = json_decode(file_get_contents($composerFile), true)['extra']['runtime'] ?? []; - - if (isset($runtimeConfig['dotenv_path'])) { - $dotenvPath = $this->projectDirectory.'/'.$runtimeConfig['dotenv_path']; - } - } - - $filePath = $dotenvPath.'/.env'; - } - - $envFiles = $this->getEnvFiles($filePath); + $envFiles = $this->getEnvFiles($dotenvPath); $availableFiles = array_filter($envFiles, 'is_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 (\in_array(\sprintf('%s.local.php', $dotenvPath), $availableFiles, true)) { + $io->warning(\sprintf('Due to existing dump file (%s.local.php) all other dotenv files are skipped.', $this->getRelativeName($dotenvPath))); } - 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))); + if (is_file($dotenvPath) && is_file(\sprintf('%s.dist', $dotenvPath))) { + $io->warning(\sprintf('The file %s.dist gets skipped due to the existence of %1$s.', $this->getRelativeName($dotenvPath))); } $io->section('Scanned Files (in descending priority)'); @@ -175,12 +163,27 @@ final class DebugCommand extends Command private function getAvailableVars(): array { - $filePath = $_SERVER['SYMFONY_DOTENV_PATH'] ?? $this->projectDirectory.\DIRECTORY_SEPARATOR.'.env'; - $envFiles = $this->getEnvFiles($filePath); + $envFiles = $this->getEnvFiles($this->getDotenvPath()); return array_keys($this->getVariables(array_filter($envFiles, 'is_file'), null)); } + private function getDotenvPath(): string + { + $config = []; + $projectDir = $this->projectDir; + + if (is_file($projectDir)) { + $config = ['dotenv_path' => basename($projectDir)]; + $projectDir = \dirname($projectDir); + } + + $composerFile = $projectDir.'/composer.json'; + $config += $_SERVER['APP_RUNTIME_OPTIONS'] ?? (is_file($composerFile) ? json_decode(file_get_contents($composerFile), true) : [])['extra']['runtime'] ?? []; + + return $projectDir.'/'.($config['dotenv_path'] ?? '.env'); + } + private function getEnvFiles(string $filePath): array { $files = [ @@ -204,8 +207,10 @@ final class DebugCommand extends Command private function getRelativeName(string $filePath): string { - if (str_starts_with($filePath, $this->projectDirectory)) { - return substr($filePath, \strlen($this->projectDirectory) + 1); + $projectDir = is_file($this->projectDir) ? \dirname($this->projectDir) : $this->projectDir; + + if (str_starts_with($filePath, $projectDir.'/')) { + return substr($filePath, \strlen($projectDir) + 1); } return basename($filePath); diff --git a/Command/DotenvDumpCommand.php b/Command/DotenvDumpCommand.php index bf57ea6..1746b2a 100644 --- a/Command/DotenvDumpCommand.php +++ b/Command/DotenvDumpCommand.php @@ -61,14 +61,8 @@ final class DotenvDumpCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { $config = []; - if (is_file($projectDir = $this->projectDir)) { - $config = ['dotenv_path' => basename($projectDir)]; - $projectDir = \dirname($projectDir); - } + $dotenvPath = $this->getDotenvPath($config); - $composerFile = $projectDir.'/composer.json'; - $config += (is_file($composerFile) ? json_decode(file_get_contents($composerFile), true) : [])['extra']['runtime'] ?? []; - $dotenvPath = $projectDir.'/'.($config['dotenv_path'] ?? '.env'); $env = $input->getArgument('env') ?? $this->defaultEnv; $envKey = $config['env_var_name'] ?? 'APP_ENV'; @@ -90,11 +84,27 @@ final class DotenvDumpCommand extends Command EOF; file_put_contents($dotenvPath.'.local.php', $vars, \LOCK_EX); - $output->writeln(\sprintf('Successfully dumped .env files in .env.local.php for the %s environment.', $env)); + $output->writeln(\sprintf('Successfully dumped %s files in %1$s.local.php for the %s environment.', basename($dotenvPath), $env)); return 0; } + private function getDotenvPath(array &$config): string + { + $config = []; + $projectDir = $this->projectDir; + + if (is_file($projectDir)) { + $config = ['dotenv_path' => basename($projectDir)]; + $projectDir = \dirname($projectDir); + } + + $composerFile = $projectDir.'/composer.json'; + $config += $_SERVER['APP_RUNTIME_OPTIONS'] ?? (is_file($composerFile) ? json_decode(file_get_contents($composerFile), true) : [])['extra']['runtime'] ?? []; + + return $projectDir.'/'.($config['dotenv_path'] ?? '.env'); + } + private function loadEnv(string $dotenvPath, string $env, array $config): array { $envKey = $config['env_var_name'] ?? 'APP_ENV'; diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php index 4e48bbe..d9f5b9f 100644 --- a/Tests/Command/DebugCommandTest.php +++ b/Tests/Command/DebugCommandTest.php @@ -294,6 +294,14 @@ class DebugCommandTest extends TestCase private function executeCommand(string $projectDirectory, string $env, array $input = [], ?string $dotenvPath = null): string { + if (null === $dotenvPath) { + unset($_SERVER['APP_RUNTIME_OPTIONS']); + } elseif (str_starts_with($dotenvPath, $projectDirectory.'/')) { + $_SERVER['APP_RUNTIME_OPTIONS'] = ['dotenv_path' => substr($dotenvPath, \strlen($projectDirectory) + 1)]; + } else { + $_SERVER['APP_RUNTIME_OPTIONS'] = ['dotenv_path' => $dotenvPath]; + } + $_SERVER['TEST_ENV_KEY'] = $env; (new Dotenv('TEST_ENV_KEY'))->bootEnv($dotenvPath ?? $projectDirectory.'/.env'); diff --git a/Tests/Command/DotenvDumpCommandTest.php b/Tests/Command/DotenvDumpCommandTest.php index 8fec23b..49bc9f0 100644 --- a/Tests/Command/DotenvDumpCommandTest.php +++ b/Tests/Command/DotenvDumpCommandTest.php @@ -20,6 +20,9 @@ class DotenvDumpCommandTest extends TestCase { protected function setUp(): void { + unset($_SERVER['SYMFONY_DOTENV_PATH']); + unset($_SERVER['APP_RUNTIME_OPTIONS']); + file_put_contents(__DIR__.'/.env', << '.env.path']; + + $command = $this->createCommand(); + $command->execute([ + 'env' => 'dev', + ]); + + $this->assertFileExists(__DIR__.'/.env.path.local.php'); + + $vars = require __DIR__.'/.env.path.local.php'; + $this->assertSame([ + 'APP_ENV' => 'dev', + 'APP_SECRET' => 'newpath123', + 'LOCAL_PATH' => 'yes', + ], $vars); + } + private function createCommand(): CommandTester { $application = new Application(); From db374255a1c99511d34d5e009dce5be75d0d9c23 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 13 Feb 2026 12:43:08 +0100 Subject: [PATCH 2/2] [Dotenv] Windows-related tweak --- Command/DebugCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index e279d90..81339f7 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -209,7 +209,7 @@ final class DebugCommand extends Command { $projectDir = is_file($this->projectDir) ? \dirname($this->projectDir) : $this->projectDir; - if (str_starts_with($filePath, $projectDir.'/')) { + if (str_starts_with($filePath, $projectDir.'/') || str_starts_with($filePath, $projectDir.\DIRECTORY_SEPARATOR)) { return substr($filePath, \strlen($projectDir) + 1); }