diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index 093f4bb1da1a0..13a619f41bf86 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -52,6 +52,7 @@ CHANGELOG
* Added a `InMemoryTransport` to Messenger. Use it with a DSN starting with `in-memory://`.
* Added `framework.property_access.throw_exception_on_invalid_property_path` config option.
* Added `cache:pool:list` command to list all available cache pools.
+ * Added `debug:autoconfiguration` command to display the autoconfiguration of interfaces/classes
4.2.0
-----
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php
new file mode 100644
index 0000000000000..6c6bf58ce9a1c
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php
@@ -0,0 +1,169 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\Command;
+
+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\Console\Style\SymfonyStyle;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Dumper\YamlDumper;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\AbstractDumper;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+/**
+ * A console command for autoconfiguration information.
+ *
+ * @internal
+ */
+final class DebugAutoconfigurationCommand extends Command
+{
+ protected static $defaultName = 'debug:autoconfiguration';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this
+ ->setDefinition([
+ new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'),
+ new InputOption('tags', null, InputOption::VALUE_NONE, 'Displays autoconfiguration interfaces/class grouped by tags'),
+ ])
+ ->setDescription('Displays current autoconfiguration for an application')
+ ->setHelp(<<<'EOF'
+The %command.name% command displays all services that
+are autoconfigured:
+
+ php %command.full_name%
+
+You can also pass a search term to filter the list:
+
+ php %command.full_name% log
+
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output);
+ $errorIo = $io->getErrorStyle();
+
+ $autoconfiguredInstanceofItems = $this->getContainerBuilder()->getAutoconfiguredInstanceof();
+
+ if ($search = $input->getArgument('search')) {
+ $autoconfiguredInstanceofItems = array_filter($autoconfiguredInstanceofItems, function ($key) use ($search) {
+ return false !== stripos(str_replace('\\', '', $key), $search);
+ }, ARRAY_FILTER_USE_KEY);
+
+ if (!$autoconfiguredInstanceofItems) {
+ $errorIo->error(sprintf('No autoconfiguration interface/class found matching "%s"', $search));
+
+ return 1;
+ }
+ }
+
+ ksort($autoconfiguredInstanceofItems, SORT_NATURAL);
+
+ $io->title('Autoconfiguration');
+ if ($search) {
+ $io->text(sprintf('(only showing classes/interfaces matching %s)', $search));
+ }
+ $io->newLine();
+
+ /** @var ChildDefinition $autoconfiguredInstanceofItem */
+ foreach ($autoconfiguredInstanceofItems as $key => $autoconfiguredInstanceofItem) {
+ $tableRows = [];
+
+ foreach ($autoconfiguredInstanceofItem->getTags() as $tag => $tagAttributes) {
+ $tableRows[] = ['Tag', $tag];
+ if ($tagAttributes !== [[]]) {
+ $tableRows[] = ['Tag attribute', $this->dumpTagAttribute($tagAttributes)];
+ }
+ }
+
+ if ($autoconfiguredInstanceofItem->getMethodCalls()) {
+ $tableRows[] = ['Method call', $this->dumpMethodCall($autoconfiguredInstanceofItem)];
+ }
+
+ if ($autoconfiguredInstanceofItem->getBindings()) {
+ $tableRows[] = ['Bindings', $this->dumpBindings($autoconfiguredInstanceofItem)];
+ }
+
+ $io->title(sprintf('Autoconfiguration for "%s"', $key));
+ $io->newLine();
+ $io->table(['Option', 'Value'], $tableRows);
+ }
+ }
+
+ private function dumpMethodCall(ChildDefinition $autoconfiguredInstanceofItem)
+ {
+ $tagContainerBuilder = new ContainerBuilder();
+ foreach ($tagContainerBuilder->getServiceIds() as $serviceId) {
+ $tagContainerBuilder->removeDefinition($serviceId);
+ $tagContainerBuilder->removeAlias($serviceId);
+ }
+ $tagContainerBuilder->addDefinitions([$autoconfiguredInstanceofItem]);
+
+ $dumper = new YamlDumper($tagContainerBuilder);
+ preg_match('/calls\:\n((?: +- .+\n)+)/', $dumper->dump(), $matches);
+
+ return preg_replace('/^\s+/m', '', $matches[1]);
+ }
+
+ private function dumpBindings(ChildDefinition $autoconfiguredInstanceofItem)
+ {
+ $tagContainerBuilder = new ContainerBuilder();
+ foreach ($tagContainerBuilder->getServiceIds() as $serviceId) {
+ $tagContainerBuilder->removeDefinition($serviceId);
+ $tagContainerBuilder->removeAlias($serviceId);
+ }
+
+ $dumper = new YamlDumper($tagContainerBuilder);
+ foreach ($autoconfiguredInstanceofItem->getBindings() as $bindingKey => $bindingValue) {
+ $tagContainerBuilder->setParameter($bindingKey, $bindingValue->getValues()[0]);
+ }
+
+ preg_match('/parameters\:\n((?: + .+\n)+)/', $dumper->dump(), $matches);
+
+ return preg_replace('/^\s+/m', '', $matches[1]);
+ }
+
+ private function dumpTagAttribute(array $tagAttribute)
+ {
+ $cloner = new VarCloner();
+ $cliDumper = new CliDumper(null, null, AbstractDumper::DUMP_LIGHT_ARRAY);
+
+ return $cliDumper->dump($cloner->cloneVar(current($tagAttribute)), true);
+ }
+
+ private function getContainerBuilder(): ContainerBuilder
+ {
+ $kernel = $this->getApplication()->getKernel();
+ $buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
+ $container = $buildContainer();
+ $container->getCompilerPassConfig()->setRemovingPasses([]);
+ $container->getCompilerPassConfig()->setAfterRemovingPasses([]);
+ $container->compile();
+
+ return $container;
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 1d83148ff755c..a853da3955682 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -404,7 +404,9 @@ public function load(array $configs, ContainerBuilder $container)
$container->registerForAutoconfiguration(LocaleAwareInterface::class)
->addTag('kernel.locale_aware');
$container->registerForAutoconfiguration(ResetInterface::class)
- ->addTag('kernel.reset', ['method' => 'reset']);
+ ->addTag('kernel.reset', ['method' => 'reset'])
+ ->addTag('kernel.reset2', ['method' => 'reset2'])
+ ;
if (!interface_exists(MarshallerInterface::class)) {
$container->registerForAutoconfiguration(ResettableInterface::class)
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
index f13aa759d31cd..56dea175ee901 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
@@ -70,6 +70,10 @@
+
+
+
+
null
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/Bindings.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/Bindings.php
new file mode 100644
index 0000000000000..903fad94f3309
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/Bindings.php
@@ -0,0 +1,15 @@
+paramOne = $paramOne;
+ $this->paramTwo = $paramTwo;
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/MethodCalls.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/MethodCalls.php
new file mode 100644
index 0000000000000..52c78d1d77be8
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/MethodCalls.php
@@ -0,0 +1,14 @@
+registerForAutoconfiguration(MethodCalls::class)
+ ->addMethodCall('setMethodOne', [new Reference('logger')])
+ ->addMethodCall('setMethodTwo', [['paramOne', 'paramOne']]);
+
+ $container->registerForAutoconfiguration(Bindings::class)
+ ->setBindings([
+ '$paramOne' => new Reference('logger'),
+ '$paramTwo' => 'binding test',
+ ]);
+
+ $container->registerForAutoconfiguration(TagsAttributes::class)
+ ->addTag('debugautoconfiguration.tag1', ['method' => 'debug'])
+ ->addTag('debugautoconfiguration.tag2', ['test'])
+ ;
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutoconfigurationCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutoconfigurationCommandTest.php
new file mode 100644
index 0000000000000..1ca56095265e5
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutoconfigurationCommandTest.php
@@ -0,0 +1,145 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
+
+use Symfony\Bundle\FrameworkBundle\Console\Application;
+use Symfony\Component\Console\Tester\ApplicationTester;
+
+/**
+ * @group functional
+ */
+class DebugAutoconfigurationCommandTest extends AbstractWebTestCase
+{
+ public function testBasicFunctionality()
+ {
+ 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:autoconfiguration']);
+
+ $expectedOutput = <<assertStringContainsString($expectedOutput, $tester->getDisplay(true));
+ }
+
+ public function testSearchArgument()
+ {
+ 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:autoconfiguration', 'search' => 'logger']);
+
+ $this->assertStringContainsString('Psr\Log\LoggerAwareInterface', $tester->getDisplay(true));
+ $this->assertStringNotContainsString('Sensio\Bundle\FrameworkExtraBundle', $tester->getDisplay(true));
+ }
+
+ public function testAutoconfigurationWithMethodCalls()
+ {
+ static::bootKernel(['test_case' => 'DebugAutoconfiguration', 'root_config' => 'config.yml']);
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(['command' => 'debug:autoconfiguration', 'search' => 'MethodCalls']);
+
+ $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DebugAutoconfigurationBundle\Autoconfiguration\MethodCalls', $tester->getDisplay(true));
+ $expectedMethodCallOutput = <<assertStringContainsString($expectedMethodCallOutput, $tester->getDisplay(true));
+ }
+
+ public function testAutoconfigurationWithMultipleTagsAttributes()
+ {
+ static::bootKernel(['test_case' => 'DebugAutoconfiguration', 'root_config' => 'config.yml']);
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(['command' => 'debug:autoconfiguration', 'search' => 'TagsAttributes']);
+
+ $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DebugAutoconfigurationBundle\Autoconfiguration\TagsAttributes', $tester->getDisplay(true));
+ $expectedTagsAttributesOutput = << "debug"
+ ]
+
+ Tag debugautoconfiguration.tag2
+ Tag attribute [
+ "test"
+ ]
+EOD;
+ $this->assertStringContainsString($expectedTagsAttributesOutput, $tester->getDisplay(true));
+ }
+
+ public function testAutoconfigurationWithBindings()
+ {
+ static::bootKernel(['test_case' => 'DebugAutoconfiguration', 'root_config' => 'config.yml']);
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(['command' => 'debug:autoconfiguration', 'search' => 'Bindings']);
+
+ $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DebugAutoconfigurationBundle\Autoconfiguration\Bindings', $tester->getDisplay(true));
+ $expectedTagsAttributesOutput = <<<'EOD'
+ Bindings $paramOne: '@logger'
+ $paramTwo: 'binding test'
+EOD;
+ $this->assertStringContainsString($expectedTagsAttributesOutput, $tester->getDisplay(true));
+ }
+
+ public function testSearchIgnoreBackslashWhenFindingInterfaceOrClass()
+ {
+ 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:autoconfiguration', 'search' => 'PsrLogLoggerAwareInterface']);
+ $this->assertStringContainsString('Psr\Log\LoggerAwareInterface', $tester->getDisplay(true));
+ }
+
+ public function testSearchNoResults()
+ {
+ 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:autoconfiguration', 'search' => 'foo_fake'], ['capture_stderr_separately' => true]);
+
+ $this->assertStringContainsString('No autoconfiguration interface/class found matching "foo_fake"', $tester->getErrorOutput());
+ $this->assertSame(1, $tester->getStatusCode());
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/bundles.php
new file mode 100644
index 0000000000000..9d2801b13df9d
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/bundles.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
+use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DebugAutoconfigurationBundle\DebugAutoconfigurationBundle;
+
+return [
+ new FrameworkBundle(),
+ new DebugAutoconfigurationBundle(),
+];
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/config.yml
new file mode 100644
index 0000000000000..f76eb28f92587
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/config.yml
@@ -0,0 +1,3 @@
+imports:
+- { resource: ../config/default.yml }
+