* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Dotenv\Command; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Dotenv\Dotenv; /** * A console command to debug current dotenv files with variables and values. * * @author Christopher Hertel */ #[AsCommand(name: 'debug:dotenv', description: 'List all dotenv files with variables and values')] final class DebugCommand extends Command { /** * @deprecated since Symfony 6.1 */ protected static $defaultName = 'debug:dotenv'; /** * @deprecated since Symfony 6.1 */ protected static $defaultDescription = 'List all dotenv files with variables and values'; private string $kernelEnvironment; private string $projectDirectory; public function __construct(string $kernelEnvironment, string $projectDirectory) { $this->kernelEnvironment = $kernelEnvironment; $this->projectDirectory = $projectDirectory; parent::__construct(); } protected function configure(): void { $this ->setDefinition([ new InputArgument('filter', InputArgument::OPTIONAL, 'The name of an environment variable or a filter.', null, $this->getAvailableVars(...)), ]) ->setHelp(<<<'EOT' The %command.full_name% command displays all the environment variables configured by dotenv: php %command.full_name% To get specific variables, specify its full or partial name: php %command.full_name% FOO_BAR EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $io->title('Dotenv Variables & Files'); if (!\array_key_exists('SYMFONY_DOTENV_VARS', $_SERVER)) { $io->error('Dotenv component is not initialized.'); return 1; } $filePath = $_SERVER['SYMFONY_DOTENV_PATH'] ?? $this->projectDirectory.\DIRECTORY_SEPARATOR.'.env'; $envFiles = $this->getEnvFiles($filePath); $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 (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(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); $io->section('Variables'); if ($variables || null === $nameFilter) { $io->table( array_merge(['Variable', 'Value'], array_map($this->getRelativeName(...), $availableFiles)), $variables ); $io->comment('Note that values might be different between web and CLI.'); } else { $io->warning(sprintf('No variables match the given filter "%s".', $nameFilter)); } return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('filter')) { $suggestions->suggestValues($this->getAvailableVars()); } } private function getVariables(array $envFiles, ?string $nameFilter): array { $variables = []; $fileValues = []; $dotenvVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? '')); foreach ($envFiles as $envFile) { $fileValues[$envFile] = $this->loadValues($envFile); $variables += $fileValues[$envFile]; } foreach ($variables as $var => $varDetails) { if (null !== $nameFilter && 0 !== stripos($var, $nameFilter)) { unset($variables[$var]); continue; } $realValue = $_SERVER[$var] ?? ''; $varDetails = [$var, ''.OutputFormatter::escape($realValue).'']; $varSeen = !isset($dotenvVars[$var]); foreach ($envFiles as $envFile) { if (null === $value = $fileValues[$envFile][$var] ?? null) { $varDetails[] = 'n/a'; continue; } $shortenedValue = OutputFormatter::escape($this->getHelper('formatter')->truncate($value, 30)); $varDetails[] = $value === $realValue && !$varSeen ? ''.$shortenedValue.'' : $shortenedValue; $varSeen = $varSeen || $value === $realValue; } $variables[$var] = $varDetails; } ksort($variables); return $variables; } private function getAvailableVars(): array { $filePath = $_SERVER['SYMFONY_DOTENV_PATH'] ?? $this->projectDirectory.\DIRECTORY_SEPARATOR.'.env'; $envFiles = $this->getEnvFiles($filePath); return array_keys($this->getVariables(array_filter($envFiles, 'is_file'), null)); } private function getEnvFiles(string $filePath): array { $files = [ sprintf('%s.local.php', $filePath), sprintf('%s.%s.local', $filePath, $this->kernelEnvironment), sprintf('%s.%s', $filePath, $this->kernelEnvironment), ]; if ('test' !== $this->kernelEnvironment) { $files[] = sprintf('%s.local', $filePath); } if (!is_file($filePath) && is_file(sprintf('%s.dist', $filePath))) { $files[] = sprintf('%s.dist', $filePath); } else { $files[] = $filePath; } return $files; } private function getRelativeName(string $filePath): string { if (str_starts_with($filePath, $this->projectDirectory)) { return substr($filePath, \strlen($this->projectDirectory) + 1); } return basename($filePath); } private function loadValues(string $filePath): array { if (str_ends_with($filePath, '.php')) { return include $filePath; } return (new Dotenv())->parse(file_get_contents($filePath)); } }