Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f1b8b6e

Browse filesBrowse files
committed
feature #42580 [Console][FrameworkBundle] Add DotenvDebugCommand (chr-hertel)
This PR was merged into the 5.4 branch. Discussion ---------- [Console][FrameworkBundle] Add DotenvDebugCommand | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #41958 | License | MIT | Doc PR | symfony/symfony-docs#15734 ![Bildschirmfoto von 2021-08-15 13-57-48](https://user-images.githubusercontent.com/2852185/129477842-06f0c680-b311-4411-8734-57579adeadeb.png) - [x] Command Implementation - [x] Tests - [x] Docs Commits ------- 5b2ecee Add DotenvDebugCommand
2 parents 7a7240f + 5b2ecee commit f1b8b6e
Copy full SHA for f1b8b6e

File tree

14 files changed

+325
-0
lines changed
Filter options

14 files changed

+325
-0
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
use Symfony\Component\DependencyInjection\Parameter;
5959
use Symfony\Component\DependencyInjection\Reference;
6060
use Symfony\Component\DependencyInjection\ServiceLocator;
61+
use Symfony\Component\Dotenv\Command\DebugCommand;
6162
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
6263
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
6364
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
@@ -252,6 +253,10 @@ public function load(array $configs, ContainerBuilder $container)
252253
if (!class_exists(BaseYamlLintCommand::class)) {
253254
$container->removeDefinition('console.command.yaml_lint');
254255
}
256+
257+
if (!class_exists(DebugCommand::class)) {
258+
$container->removeDefinition('console.command.dotenv_debug');
259+
}
255260
}
256261

257262
// Load Cache configuration first as it is used by other components

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand;
4040
use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber;
4141
use Symfony\Component\Console\EventListener\ErrorListener;
42+
use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand;
4243
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
4344
use Symfony\Component\Messenger\Command\DebugCommand;
4445
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
@@ -129,6 +130,13 @@
129130
])
130131
->tag('console.command')
131132

133+
->set('console.command.dotenv_debug', DotenvDebugCommand::class)
134+
->args([
135+
param('kernel.environment'),
136+
param('kernel.project_dir'),
137+
])
138+
->tag('console.command')
139+
132140
->set('console.command.event_dispatcher_debug', EventDispatcherDebugCommand::class)
133141
->args([
134142
tagged_locator('event_dispatcher.dispatcher', 'name'),

‎src/Symfony/Component/Dotenv/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Dotenv/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add `dotenv:dump` command to compile the contents of the .env files into a PHP-optimized file called `.env.local.php`
8+
* Add `debug:dotenv` command to list all dotenv files with variables and values
89

910
5.1.0
1011
-----
+143Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dotenv\Command;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
use Symfony\Component\Console\Style\SymfonyStyle;
18+
use Symfony\Component\Dotenv\Dotenv;
19+
20+
/**
21+
* A console command to debug current dotenv files with variables and values.
22+
*
23+
* @author Christopher Hertel <mail@christopher-hertel.de>
24+
*/
25+
final class DebugCommand extends Command
26+
{
27+
protected static $defaultName = 'debug:dotenv';
28+
protected static $defaultDescription = 'Lists all dotenv files with variables and values';
29+
30+
private $kernelEnvironment;
31+
private $projectDirectory;
32+
33+
public function __construct(string $kernelEnvironment, string $projectDirectory)
34+
{
35+
$this->kernelEnvironment = $kernelEnvironment;
36+
$this->projectDirectory = $projectDirectory;
37+
38+
parent::__construct();
39+
}
40+
41+
protected function execute(InputInterface $input, OutputInterface $output): int
42+
{
43+
$io = new SymfonyStyle($input, $output);
44+
$io->title('Dotenv Variables & Files');
45+
46+
if (!\array_key_exists('SYMFONY_DOTENV_VARS', $_SERVER)) {
47+
$io->error('Dotenv component is not initialized.');
48+
49+
return 1;
50+
}
51+
52+
$envFiles = $this->getEnvFiles();
53+
$availableFiles = array_filter($envFiles, function (string $file) {
54+
return is_file($this->getFilePath($file));
55+
});
56+
57+
if (\in_array('.env.local.php', $availableFiles, true)) {
58+
$io->warning('Due to existing dump file (.env.local.php) all other dotenv files are skipped.');
59+
}
60+
61+
if (is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) {
62+
$io->warning('The file .env.dist gets skipped due to the existence of .env.');
63+
}
64+
65+
$io->section('Scanned Files (in descending priority)');
66+
$io->listing(array_map(static function (string $envFile) use ($availableFiles) {
67+
return \in_array($envFile, $availableFiles, true)
68+
? sprintf('<fg=green>✓</> %s', $envFile)
69+
: sprintf('<fg=red>⨯</> %s', $envFile);
70+
}, $envFiles));
71+
72+
$io->section('Variables');
73+
$io->table(
74+
array_merge(['Variable', 'Value'], $availableFiles),
75+
$this->getVariables($availableFiles)
76+
);
77+
78+
$io->comment('Note real values might be different between web and CLI.');
79+
80+
return 0;
81+
}
82+
83+
private function getVariables(array $envFiles): array
84+
{
85+
$vars = explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? '');
86+
sort($vars);
87+
88+
$output = [];
89+
$fileValues = [];
90+
foreach ($vars as $var) {
91+
$realValue = $_SERVER[$var];
92+
$varDetails = [$var, $realValue];
93+
foreach ($envFiles as $envFile) {
94+
$values = $fileValues[$envFile] ?? $fileValues[$envFile] = $this->loadValues($envFile);
95+
96+
$varString = $values[$var] ?? '<fg=yellow>n/a</>';
97+
$shortenedVar = $this->getHelper('formatter')->truncate($varString, 30);
98+
$varDetails[] = $varString === $realValue ? '<fg=green>'.$shortenedVar.'</>' : $shortenedVar;
99+
}
100+
101+
$output[] = $varDetails;
102+
}
103+
104+
return $output;
105+
}
106+
107+
private function getEnvFiles(): array
108+
{
109+
$files = [
110+
'.env.local.php',
111+
sprintf('.env.%s.local', $this->kernelEnvironment),
112+
sprintf('.env.%s', $this->kernelEnvironment),
113+
];
114+
115+
if ('test' !== $this->kernelEnvironment) {
116+
$files[] = '.env.local';
117+
}
118+
119+
if (!is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) {
120+
$files[] = '.env.dist';
121+
} else {
122+
$files[] = '.env';
123+
}
124+
125+
return $files;
126+
}
127+
128+
private function getFilePath(string $file): string
129+
{
130+
return $this->projectDirectory.\DIRECTORY_SEPARATOR.$file;
131+
}
132+
133+
private function loadValues(string $file): array
134+
{
135+
$filePath = $this->getFilePath($file);
136+
137+
if (str_ends_with($filePath, '.php')) {
138+
return include $filePath;
139+
}
140+
141+
return (new Dotenv())->parse(file_get_contents($filePath));
142+
}
143+
}
+153Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dotenv\Tests\Command;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Console\Helper\FormatterHelper;
16+
use Symfony\Component\Console\Helper\HelperSet;
17+
use Symfony\Component\Console\Tester\CommandTester;
18+
use Symfony\Component\Dotenv\Command\DebugCommand;
19+
use Symfony\Component\Dotenv\Dotenv;
20+
21+
class DebugCommandTest extends TestCase
22+
{
23+
/**
24+
* @runInSeparateProcess
25+
*/
26+
public function testErrorOnUninitializedDotenv()
27+
{
28+
$command = new DebugCommand('dev', __DIR__.'/Fixtures/Scenario1');
29+
$command->setHelperSet(new HelperSet([new FormatterHelper()]));
30+
$tester = new CommandTester($command);
31+
$tester->execute([]);
32+
$output = $tester->getDisplay();
33+
34+
$this->assertStringContainsString('[ERROR] Dotenv component is not initialized', $output);
35+
}
36+
37+
public function testScenario1InDevEnv()
38+
{
39+
$output = $this->executeCommand(__DIR__.'/Fixtures/Scenario1', 'dev');
40+
41+
// Scanned Files
42+
$this->assertStringContainsString('⨯ .env.local.php', $output);
43+
$this->assertStringContainsString('⨯ .env.dev.local', $output);
44+
$this->assertStringContainsString('⨯ .env.dev', $output);
45+
$this->assertStringContainsString('✓ .env.local', $output);
46+
$this->assertStringContainsString('✓ .env'.\PHP_EOL, $output);
47+
48+
// Skipped Files
49+
$this->assertStringNotContainsString('.env.prod', $output);
50+
$this->assertStringNotContainsString('.env.test', $output);
51+
$this->assertStringNotContainsString('.env.dist', $output);
52+
53+
// Variables
54+
$this->assertStringContainsString('Variable Value .env.local .env', $output);
55+
$this->assertStringContainsString('FOO baz baz bar', $output);
56+
$this->assertStringContainsString('TEST123 true n/a true', $output);
57+
}
58+
59+
public function testScenario1InTestEnv()
60+
{
61+
$output = $this->executeCommand(__DIR__.'/Fixtures/Scenario1', 'test');
62+
63+
// Scanned Files
64+
$this->assertStringContainsString('⨯ .env.local.php', $output);
65+
$this->assertStringContainsString('⨯ .env.test.local', $output);
66+
$this->assertStringContainsString('✓ .env.test', $output);
67+
$this->assertStringContainsString('✓ .env'.\PHP_EOL, $output);
68+
69+
// Skipped Files
70+
$this->assertStringNotContainsString('.env.prod', $output);
71+
$this->assertStringNotContainsString('.env.dev', $output);
72+
$this->assertStringNotContainsString('.env.dist', $output);
73+
74+
// Variables
75+
$this->assertStringContainsString('Variable Value .env.test .env', $output);
76+
$this->assertStringContainsString('FOO bar n/a bar', $output);
77+
$this->assertStringContainsString('TEST123 false false true', $output);
78+
}
79+
80+
public function testScenario1InProdEnv()
81+
{
82+
$output = $this->executeCommand(__DIR__.'/Fixtures/Scenario1', 'prod');
83+
84+
// Scanned Files
85+
$this->assertStringContainsString('⨯ .env.local.php', $output);
86+
$this->assertStringContainsString('✓ .env.prod.local', $output);
87+
$this->assertStringContainsString('⨯ .env.prod', $output);
88+
$this->assertStringContainsString('✓ .env.local', $output);
89+
$this->assertStringContainsString('✓ .env'.\PHP_EOL, $output);
90+
91+
// Skipped Files
92+
$this->assertStringNotContainsString('.env.dev', $output);
93+
$this->assertStringNotContainsString('.env.test', $output);
94+
$this->assertStringNotContainsString('.env.dist', $output);
95+
96+
// Variables
97+
$this->assertStringContainsString('Variable Value .env.prod.local .env.local .env', $output);
98+
$this->assertStringContainsString('FOO baz n/a baz bar', $output);
99+
$this->assertStringContainsString('HELLO world world n/a n/a', $output);
100+
$this->assertStringContainsString('TEST123 true n/a n/a true', $output);
101+
}
102+
103+
public function testScenario2InProdEnv()
104+
{
105+
$output = $this->executeCommand(__DIR__.'/Fixtures/Scenario2', 'prod');
106+
107+
// Scanned Files
108+
$this->assertStringContainsString('✓ .env.local.php', $output);
109+
$this->assertStringContainsString('⨯ .env.prod.local', $output);
110+
$this->assertStringContainsString('✓ .env.prod', $output);
111+
$this->assertStringContainsString('⨯ .env.local', $output);
112+
$this->assertStringContainsString('✓ .env.dist', $output);
113+
114+
// Skipped Files
115+
$this->assertStringNotContainsString('.env'.\PHP_EOL, $output);
116+
$this->assertStringNotContainsString('.env.dev', $output);
117+
$this->assertStringNotContainsString('.env.test', $output);
118+
119+
// Variables
120+
$this->assertStringContainsString('Variable Value .env.local.php .env.prod .env.dist', $output);
121+
$this->assertStringContainsString('FOO BaR BaR BaR n/a', $output);
122+
$this->assertStringContainsString('TEST 1234 1234 1234 0000', $output);
123+
}
124+
125+
public function testWarningOnEnvAndEnvDistFile()
126+
{
127+
$output = $this->executeCommand(__DIR__.'/Fixtures/Scenario3', 'dev');
128+
129+
// Warning
130+
$this->assertStringContainsString('[WARNING] The file .env.dist gets skipped', $output);
131+
}
132+
133+
public function testWarningOnPhpEnvFile()
134+
{
135+
$output = $this->executeCommand(__DIR__.'/Fixtures/Scenario2', 'prod');
136+
137+
// Warning
138+
$this->assertStringContainsString('[WARNING] Due to existing dump file (.env.local.php)', $output);
139+
}
140+
141+
private function executeCommand(string $projectDirectory, string $env): string
142+
{
143+
$_SERVER['TEST_ENV_KEY'] = $env;
144+
(new Dotenv('TEST_ENV_KEY'))->bootEnv($projectDirectory.'/.env');
145+
146+
$command = new DebugCommand($env, $projectDirectory);
147+
$command->setHelperSet(new HelperSet([new FormatterHelper()]));
148+
$tester = new CommandTester($command);
149+
$tester->execute([]);
150+
151+
return $tester->getDisplay();
152+
}
153+
}
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TEST123=true
2+
FOO=bar
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FOO=baz
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
HELLO=world
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TEST123=false
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TEST=0000
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
return [
3+
'FOO' => 'BaR',
4+
'TEST' => '1234',
5+
];
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FOO=BaR
2+
TEST=1234
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FOO=BAR
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FOO=BAZ

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.