[FrameworkBundle] Debug container environment variables
This commit is contained in:
parent
041f60f80a
commit
b813a05aa5
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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}
|
||||
*/
|
||||
|
@ -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}
|
||||
*/
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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 [
|
||||
|
@ -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)%'
|
||||
|
@ -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",
|
||||
|
Reference in New Issue
Block a user