[FrameworkBundle] Debug container environment variables

This commit is contained in:
Roland Franssen 2018-06-22 12:44:17 +02:00 committed by Fabien Potencier
parent 041f60f80a
commit b813a05aa5
9 changed files with 235 additions and 7 deletions

View File

@ -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 <info>--types</i
<info>php %command.full_name% --types</info>
To see environment variables used by the container, use the <info>--env-vars</info> flag:
<info>php %command.full_name% --env-vars</info>
Display a specific environment variable by specifying its name with the <info>--env-var</info> option:
<info>php %command.full_name% --env-var=APP_ENV</info>
Use the --tags option to display tagged <comment>public</comment> services grouped by tag:
<info>php %command.full_name% --tags</info>
@ -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. <comment>debug:container --tag=form.type</comment>)');
} 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;
}

View File

@ -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);
}
}

View File

@ -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.
*

View File

@ -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}
*/

View File

@ -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([], [
['<info>Default value</>', $env['default_available'] ? $dump($env['default_value']) : 'n/a'],
['<info>Real value</>', $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a'],
['<info>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}
*/

View File

@ -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.
*

View File

@ -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 [

View File

@ -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)%'

View File

@ -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",