Files
archived-dotenv/Command/DebugCommand.php
Christian Flothmann 94d59769b0 Merge branch '7.4' into 8.0
* 7.4:
  prioritize property type over is/has/can accessors
  [Dotenv] Windows-related tweak
  [Dotenv] Use `APP_RUNTIME_OPTIONS` variable when dumping dotenv
2026-02-13 13:00:38 +01:00

218 lines
7.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <mail@christopher-hertel.de>
*/
#[AsCommand(name: 'debug:dotenv', description: 'List all dotenv files with variables and values')]
final class DebugCommand extends Command
{
public function __construct(
private string $kernelEnvironment,
private string $projectDir,
) {
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 <info>%command.full_name%</info> command displays all the environment variables configured by dotenv:
<info>php %command.full_name%</info>
To get specific variables, specify its full or partial name:
<info>php %command.full_name% FOO_BAR</info>
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;
}
$dotenvPath = $this->getDotenvPath();
$envFiles = $this->getEnvFiles($dotenvPath);
$availableFiles = array_filter($envFiles, 'is_file');
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($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)');
$io->listing(array_map(fn (string $envFile) => \in_array($envFile, $availableFiles, true)
? \sprintf('<fg=green>✓</> %s', $this->getRelativeName($envFile))
: \sprintf('<fg=red></> %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, '<fg=green>'.OutputFormatter::escape($realValue).'</>'];
$varSeen = !isset($dotenvVars[$var]);
foreach ($envFiles as $envFile) {
if (null === $value = $fileValues[$envFile][$var] ?? null) {
$varDetails[] = '<fg=yellow>n/a</>';
continue;
}
$shortenedValue = OutputFormatter::escape($this->getHelper('formatter')->truncate($value, 30));
$varDetails[] = $value === $realValue && !$varSeen ? '<fg=green>'.$shortenedValue.'</>' : $shortenedValue;
$varSeen = $varSeen || $value === $realValue;
}
$variables[$var] = $varDetails;
}
ksort($variables);
return $variables;
}
private function getAvailableVars(): array
{
$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 = [
\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
{
$projectDir = is_file($this->projectDir) ? \dirname($this->projectDir) : $this->projectDir;
if (str_starts_with($filePath, $projectDir.'/') || str_starts_with($filePath, $projectDir.\DIRECTORY_SEPARATOR)) {
return substr($filePath, \strlen($projectDir) + 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));
}
}