This commit is contained in:
Marek
2026-03-24 00:04:55 +01:00
commit c5229e48ed
4225 changed files with 511461 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
<?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
{
/**
* @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';
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));
}
}

View File

@@ -0,0 +1,130 @@
<?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\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\Dotenv\Dotenv;
/**
* A console command to compile .env files into a PHP-optimized file called .env.local.php.
*
* To use this command, first register it explicitly as a service, e.g in your services.yaml file:
*
* ```yaml
* services:
* # [...]
* Symfony\Component\Dotenv\Command\DotenvDumpCommand: ~
* ```
*/
#[Autoconfigure(bind: ['$projectDir' => '%kernel.project_dir%', '$defaultEnv' => '%kernel.environment%'])]
#[AsCommand(name: 'dotenv:dump', description: 'Compile .env files to .env.local.php')]
final class DotenvDumpCommand extends Command
{
public function __construct(
private string $projectDir,
private ?string $defaultEnv = null,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('env', null === $this->defaultEnv ? InputArgument::REQUIRED : InputArgument::OPTIONAL, 'The application environment to dump .env files for - e.g. "prod".'),
])
->addOption('empty', null, InputOption::VALUE_NONE, 'Ignore the content of .env files')
->setHelp(<<<'EOT'
The <info>%command.name%</info> command compiles .env files into a PHP-optimized file called .env.local.php.
<info>%command.full_name%</info>
EOT
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$config = [];
$dotenvPath = $this->getDotenvPath($config);
$env = $input->getArgument('env') ?? $this->defaultEnv;
$envKey = $config['env_var_name'] ?? 'APP_ENV';
if ($input->getOption('empty')) {
$vars = [$envKey => $env];
} else {
$vars = $this->loadEnv($dotenvPath, $env, $config);
$env = $vars[$envKey];
}
$vars = var_export($vars, true);
$vars = <<<EOF
<?php
// This file was generated by running "php bin/console dotenv:dump $env"
return $vars;
EOF;
file_put_contents($dotenvPath.'.local.php', $vars, \LOCK_EX);
$output->writeln(\sprintf('Successfully dumped %s files in <info>%1$s.local.php</> for the <info>%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';
$testEnvs = $config['test_envs'] ?? ['test'];
$dotenv = new Dotenv($envKey);
$globalsBackup = [$_SERVER, $_ENV];
unset($_SERVER[$envKey]);
$_ENV = [$envKey => $env];
$_SERVER['SYMFONY_DOTENV_VARS'] = implode(',', array_keys($_SERVER));
try {
$dotenv->loadEnv($dotenvPath, null, 'dev', $testEnvs);
unset($_ENV['SYMFONY_DOTENV_VARS']);
unset($_ENV['SYMFONY_DOTENV_PATH']);
return $_ENV;
} finally {
[$_SERVER, $_ENV] = $globalsBackup;
}
}
}