diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php
index 9b5193d63b..13ddc96276 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php
@@ -58,6 +58,8 @@ class ContainerDebugCommand extends Command
new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Displays a specific parameter for an application'),
new InputOption('parameters', null, InputOption::VALUE_NONE, 'Displays parameters for an application'),
new InputOption('types', null, InputOption::VALUE_NONE, 'Displays types (classes/interfaces) available in the container'),
+ new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Displays a specific environment variable used in the container'),
+ new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Displays environment variables used in the container'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
])
@@ -75,6 +77,14 @@ To see available types that can be used for autowiring, use the --typesphp %command.full_name% --types
+To see environment variables used by the container, use the --env-vars flag:
+
+ php %command.full_name% --env-vars
+
+Display a specific environment variable by specifying its name with the --env-var option:
+
+ php %command.full_name% --env-var=APP_ENV
+
Use the --tags option to display tagged public services grouped by tag:
php %command.full_name% --tags
@@ -116,7 +126,11 @@ EOF
$this->validateInput($input);
$object = $this->getContainerBuilder();
- if ($input->getOption('types')) {
+ if ($input->getOption('env-vars')) {
+ $options = ['env-vars' => true];
+ } elseif ($envVar = $input->getOption('env-var')) {
+ $options = ['env-vars' => true, 'name' => $envVar];
+ } elseif ($input->getOption('types')) {
$options = [];
$options['filter'] = [$this, 'filterToServiceTypes'];
} elseif ($input->getOption('parameters')) {
@@ -156,7 +170,7 @@ EOF
throw $e;
}
- if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && $input->isInteractive()) {
+ if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && !$input->getOption('env-vars') && !$input->getOption('env-var') && $input->isInteractive()) {
if ($input->getOption('tags')) {
$errorIo->comment('To search for a specific tag, re-run this command with a search term. (e.g. debug:container --tag=form.type)');
} elseif ($input->getOption('parameters')) {
@@ -209,12 +223,15 @@ EOF
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
$container = $buildContainer();
- $container->getCompilerPassConfig()->setRemovingPasses([]);
- $container->compile();
} else {
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
+ $container->setParameter('container.build_hash', $hash = ContainerBuilder::hash(__METHOD__));
+ $container->setParameter('container.build_id', hash('crc32', $hash.time()));
}
+ $container->getCompilerPassConfig()->setRemovingPasses([]);
+ $container->compile();
+
return $this->containerBuilder = $container;
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php
index 484ca4fefd..7babbd3912 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php
@@ -52,6 +52,9 @@ abstract class Descriptor implements DescriptorInterface
case $object instanceof ParameterBag:
$this->describeContainerParameters($object, $options);
break;
+ case $object instanceof ContainerBuilder && !empty($options['env-vars']):
+ $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options);
+ break;
case $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by']:
$this->describeContainerTags($object, $options);
break;
@@ -157,6 +160,11 @@ abstract class Descriptor implements DescriptorInterface
*/
abstract protected function describeContainerParameter($parameter, array $options = []);
+ /**
+ * Describes container environment variables.
+ */
+ abstract protected function describeContainerEnvVars(array $envs, array $options = []);
+
/**
* Describes event dispatcher listeners.
*
@@ -311,4 +319,35 @@ abstract class Descriptor implements DescriptorInterface
return '';
}
+
+ private function getContainerEnvVars(ContainerBuilder $container): array
+ {
+ $getEnvReflection = new \ReflectionMethod($container, 'getEnv');
+ $getEnvReflection->setAccessible(true);
+ $envs = [];
+ foreach (array_keys($container->getEnvCounters()) as $env) {
+ $processor = 'string';
+ if (false !== $i = strrpos($name = $env, ':')) {
+ $name = substr($env, $i + 1);
+ $processor = substr($env, 0, $i);
+ }
+ $defaultValue = ($hasDefault = $container->hasParameter("env($name)")) ? $container->getParameter("env($name)") : null;
+ if (false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name))) {
+ $runtimeValue = null;
+ }
+ $processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null;
+ $envs[$name.$processor] = [
+ 'name' => $name,
+ 'processor' => $processor,
+ 'default_available' => $hasDefault,
+ 'default_value' => $defaultValue,
+ 'runtime_available' => $hasRuntime,
+ 'runtime_value' => $runtimeValue,
+ 'processed_value' => $processedValue,
+ ];
+ }
+ ksort($envs);
+
+ return array_values($envs);
+ }
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php
index 137114ebe7..e50f50722b 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php
@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
+use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -177,6 +178,14 @@ class JsonDescriptor extends Descriptor
$this->writeData([$key => $parameter], $options);
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeContainerEnvVars(array $envs, array $options = [])
+ {
+ throw new LogicException('Using the JSON format to debug environment variables is not supported.');
+ }
+
/**
* Writes data as json.
*
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php
index e68152b9a5..162e68f84d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php
@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
+use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
@@ -273,6 +274,14 @@ class MarkdownDescriptor extends Descriptor
$this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter);
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeContainerEnvVars(array $envs, array $options = [])
+ {
+ throw new LogicException('Using the markdown format to debug environment variables is not supported.');
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
index 8980a9ee41..1861916896 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Helper\Dumper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Alias;
@@ -384,6 +385,71 @@ class TextDescriptor extends Descriptor
]);
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeContainerEnvVars(array $envs, array $options = [])
+ {
+ $dump = new Dumper($this->output);
+ $options['output']->title('Symfony Container Environment Variables');
+
+ if (null !== $name = $options['name'] ?? null) {
+ $options['output']->comment('Displaying detailed environment variable usage matching '.$name);
+
+ $matches = false;
+ foreach ($envs as $env) {
+ if ($name === $env['name'] || false !== stripos($env['name'], $name)) {
+ $matches = true;
+ $options['output']->section('%env('.$env['processor'].':'.$env['name'].')%');
+ $options['output']->table([], [
+ ['Default value>', $env['default_available'] ? $dump($env['default_value']) : 'n/a'],
+ ['Real value>', $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a'],
+ ['Processed value>', $env['default_available'] || $env['runtime_available'] ? $dump($env['processed_value']) : 'n/a'],
+ ]);
+ }
+ }
+
+ if (!$matches) {
+ $options['output']->block('None of the environment variables match this name.');
+ } else {
+ $options['output']->comment('Note real values might be different between web and CLI.');
+ }
+
+ return;
+ }
+
+ if (!$envs) {
+ $options['output']->block('No environment variables are being used.');
+
+ return;
+ }
+
+ $rows = [];
+ $missing = [];
+ foreach ($envs as $env) {
+ if (isset($rows[$env['name']])) {
+ continue;
+ }
+
+ $rows[$env['name']] = [
+ $env['name'],
+ $env['default_available'] ? $dump($env['default_value']) : 'n/a',
+ $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a',
+ ];
+ if (!$env['default_available'] && !$env['runtime_available']) {
+ $missing[$env['name']] = true;
+ }
+ }
+
+ $options['output']->table(['Name', 'Default value', 'Real value'], $rows);
+ $options['output']->comment('Note real values might be different between web and CLI.');
+
+ if ($missing) {
+ $options['output']->warning('The following variables are missing:');
+ $options['output']->listing(array_keys($missing));
+ }
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php
index db0f346ebd..a58c837823 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php
@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
+use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -131,6 +132,14 @@ class XmlDescriptor extends Descriptor
$this->writeDocument($this->getContainerParameterDocument($parameter, $options));
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeContainerEnvVars(array $envs, array $options = [])
+ {
+ throw new LogicException('Using the XML format to debug environment variables is not supported.');
+ }
+
/**
* Writes DOM document.
*
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php
index ee13386a4b..310646e428 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php
@@ -80,6 +80,75 @@ class ContainerDebugCommandTest extends WebTestCase
$this->assertNotContains('No services found', $tester->getDisplay());
}
+ public function testDescribeEnvVars()
+ {
+ putenv('REAL=value');
+ static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']);
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(['command' => 'debug:container', '--env-vars' => true], ['decorated' => false]);
+
+ $this->assertStringMatchesFormat(<<<'TXT'
+
+Symfony Container Environment Variables
+=======================================
+
+ --------- ----------------- ------------
+ Name Default value Real value
+ --------- ----------------- ------------
+ JSON "[1, "2.5", 3]" n/a
+ REAL n/a "value"
+ UNKNOWN n/a n/a
+ --------- ----------------- ------------
+
+ // Note real values might be different between web and CLI.%w
+
+ [WARNING] The following variables are missing:%w
+
+ * UNKNOWN
+
+TXT
+ , $tester->getDisplay(true));
+
+ putenv('REAL');
+ }
+
+ public function testDescribeEnvVar()
+ {
+ static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']);
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(['command' => 'debug:container', '--env-var' => 'js'], ['decorated' => false]);
+
+ $this->assertContains(<<<'TXT'
+%env(float:key:2:json:JSON)%
+----------------------------
+
+ ----------------- -----------------
+ Default value "[1, "2.5", 3]"
+ Real value n/a
+ Processed value 3.0
+ ----------------- -----------------
+
+%env(int:key:2:json:JSON)%
+--------------------------
+
+ ----------------- -----------------
+ Default value "[1, "2.5", 3]"
+ Real value n/a
+ Processed value 3
+ ----------------- -----------------
+
+TXT
+ , $tester->getDisplay(true));
+ }
+
public function provideIgnoreBackslashWhenFindingService()
{
return [
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml
index fb3b3b9b9a..0cc73dbc81 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml
@@ -1,6 +1,9 @@
imports:
- { resource: ../config/default.yml }
+parameters:
+ env(JSON): '[1, "2.5", 3]'
+
services:
_defaults: { public: true }
public:
@@ -10,3 +13,10 @@ services:
public: false
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass:
class: Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass
+ env:
+ class: stdClass
+ arguments:
+ - '%env(UNKNOWN)%'
+ - '%env(REAL)%'
+ - '%env(int:key:2:json:JSON)%'
+ - '%env(float:key:2:json:JSON)%'
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index 681dd45fd3..57a7e25623 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -34,7 +34,7 @@
"fig/link-util": "^1.0",
"symfony/asset": "~3.4|~4.0",
"symfony/browser-kit": "^4.3",
- "symfony/console": "~3.4|~4.0",
+ "symfony/console": "^4.3",
"symfony/css-selector": "~3.4|~4.0",
"symfony/dom-crawler": "~3.4|~4.0",
"symfony/polyfill-intl-icu": "~1.0",
@@ -53,7 +53,7 @@
"symfony/templating": "~3.4|~4.0",
"symfony/twig-bundle": "~2.8|~3.2|~4.0",
"symfony/validator": "^4.1",
- "symfony/var-dumper": "~3.4|~4.0",
+ "symfony/var-dumper": "^4.3",
"symfony/workflow": "^4.3",
"symfony/yaml": "~3.4|~4.0",
"symfony/property-info": "~3.4|~4.0",
@@ -69,7 +69,7 @@
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
"symfony/asset": "<3.4",
"symfony/browser-kit": "<4.3",
- "symfony/console": "<3.4",
+ "symfony/console": "<4.3",
"symfony/dotenv": "<4.2",
"symfony/form": "<4.3",
"symfony/messenger": "<4.3",