diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index 75b89f95e5b6a..54c94ff4279d7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -23,6 +23,7 @@ CHANGELOG
* Deprecated `CachePoolClearerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass` instead.
* Deprecated `CachePoolPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPass` instead.
* Deprecated `CachePoolPrunerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass` instead.
+ * Added `debug:autoconfiguration` command to display the autoconfiguration of interfaces/classes
4.1.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..fb620cc255310
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php
@@ -0,0 +1,171 @@
+
+ *
+ * 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
+ */
+class DebugAutoconfigurationCommand extends Command
+{
+ protected static $defaultName = 'debug:autoconfiguration';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this
+ ->setDefinition(array(
+ 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 (empty($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 = array();
+
+ foreach ($autoconfiguredInstanceofItem->getTags() as $tag => $tagAttributes) {
+ $tableRows[] = array('Tag', $tag);
+ if ($tagAttributes !== array(array())) {
+ $tableRows[] = array('Tag attribute', $this->dumpTagAttribute($tagAttributes));
+ }
+ }
+
+ if ($autoconfiguredInstanceofItem->getMethodCalls()) {
+ $tableRows[] = array('Method call', $this->dumpMethodCall($autoconfiguredInstanceofItem));
+ }
+
+ if ($autoconfiguredInstanceofItem->getBindings()) {
+ $tableRows[] = array('Bindings', $this->dumpBindings($autoconfiguredInstanceofItem));
+ }
+
+ $io->writeln(sprintf("Autoconfiguration for \"%s\"\n==============================================", $key));
+ $io->newLine();
+ $io->table(array('Option', 'Value'), $tableRows);
+ }
+ }
+
+ private function dumpMethodCall(ChildDefinition $autoconfiguredInstanceofItem)
+ {
+ $tagContainerBuilder = new ContainerBuilder();
+ foreach ($tagContainerBuilder->getServiceIds() as $serviceId) {
+ $tagContainerBuilder->removeDefinition($serviceId);
+ $tagContainerBuilder->removeAlias($serviceId);
+ }
+ $tagContainerBuilder->addDefinitions(array($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);
+ }
+
+ /**
+ * @return ContainerBuilder
+ */
+ private function getContainerBuilder()
+ {
+ $kernel = $this->getApplication()->getKernel();
+ $buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
+ $container = $buildContainer();
+ $container->getCompilerPassConfig()->setRemovingPasses(array());
+ $container->compile();
+
+ return $container;
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 2e64bff2c03d7..7e55f305ff0c6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -336,7 +336,9 @@ public function load(array $configs, ContainerBuilder $container)
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
->addTag('kernel.event_subscriber');
$container->registerForAutoconfiguration(ResetInterface::class)
- ->addTag('kernel.reset', array('method' => 'reset'));
+ ->addTag('kernel.reset', array('method' => 'reset'))
+ ->addTag('kernel.reset2', array('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 9553d4d60d7ce..8ec48cfa73506 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
@@ -60,6 +60,10 @@
+
+
+
+
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', array(new Reference('logger')))
+ ->addMethodCall('setMethodTwo', array(array('paramOne', 'paramOne')));
+
+ $container->registerForAutoconfiguration(Bindings::class)
+ ->setBindings(array(
+ '$paramOne' => new Reference('logger'),
+ '$paramTwo' => 'binding test',
+ ));
+
+ $container->registerForAutoconfiguration(TagsAttributes::class)
+ ->addTag('debugautoconfiguration.tag1', array('method' => 'debug'))
+ ->addTag('debugautoconfiguration.tag2', array('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..504200effb4f3
--- /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 WebTestCase
+{
+ public function testBasicFunctionality()
+ {
+ static::bootKernel(array('test_case' => 'ContainerDebug', 'root_config' => 'config.yml'));
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(array('command' => 'debug:autoconfiguration'));
+
+ $expectedOutput = <<assertContains($expectedOutput, $tester->getDisplay(true));
+ }
+
+ public function testSearchArgument()
+ {
+ static::bootKernel(array('test_case' => 'ContainerDebug', 'root_config' => 'config.yml'));
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(array('command' => 'debug:autoconfiguration', 'search' => 'logger'));
+
+ $this->assertContains('Psr\Log\LoggerAwareInterface', $tester->getDisplay(true));
+ $this->assertNotContains('Sensio\Bundle\FrameworkExtraBundle', $tester->getDisplay(true));
+ }
+
+ public function testAutoconfigurationWithMethodCalls()
+ {
+ static::bootKernel(array('test_case' => 'DebugAutoconfiguration', 'root_config' => 'config.yml'));
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(array('command' => 'debug:autoconfiguration', 'search' => 'MethodCalls'));
+
+ $this->assertContains('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DebugAutoconfigurationBundle\Autoconfiguration\MethodCalls', $tester->getDisplay(true));
+ $expectedMethodCallOutput = <<assertContains($expectedMethodCallOutput, $tester->getDisplay(true));
+ }
+
+ public function testAutoconfigurationWithMultipleTagsAttributes()
+ {
+ static::bootKernel(array('test_case' => 'DebugAutoconfiguration', 'root_config' => 'config.yml'));
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(array('command' => 'debug:autoconfiguration', 'search' => 'TagsAttributes'));
+
+ $this->assertContains('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DebugAutoconfigurationBundle\Autoconfiguration\TagsAttributes', $tester->getDisplay(true));
+ $expectedTagsAttributesOutput = << "debug"
+ ]
+
+ Tag debugautoconfiguration.tag2
+ Tag attribute [
+ "test"
+ ]
+EOD;
+ $this->assertContains($expectedTagsAttributesOutput, $tester->getDisplay(true));
+ }
+
+ public function testAutoconfigurationWithBindings()
+ {
+ static::bootKernel(array('test_case' => 'DebugAutoconfiguration', 'root_config' => 'config.yml'));
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(array('command' => 'debug:autoconfiguration', 'search' => 'Bindings'));
+
+ $this->assertContains('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DebugAutoconfigurationBundle\Autoconfiguration\Bindings', $tester->getDisplay(true));
+ $expectedTagsAttributesOutput = <<<'EOD'
+ Bindings $paramOne: '@logger'
+ $paramTwo: 'binding test'
+EOD;
+ $this->assertContains($expectedTagsAttributesOutput, $tester->getDisplay(true));
+ }
+
+ public function testSearchIgnoreBackslashWhenFindingInterfaceOrClass()
+ {
+ static::bootKernel(array('test_case' => 'ContainerDebug', 'root_config' => 'config.yml'));
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(array('command' => 'debug:autoconfiguration', 'search' => 'PsrLogLoggerAwareInterface'));
+ $this->assertContains('Psr\Log\LoggerAwareInterface', $tester->getDisplay(true));
+ }
+
+ public function testSearchNoResults()
+ {
+ static::bootKernel(array('test_case' => 'ContainerDebug', 'root_config' => 'config.yml'));
+
+ $application = new Application(static::$kernel);
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+ $tester->run(array('command' => 'debug:autoconfiguration', 'search' => 'foo_fake'), array('capture_stderr_separately' => true));
+
+ $this->assertContains('No autoconfiguration interface/class found matching "foo_fake"', $tester->getErrorOutput());
+ $this->assertEquals(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..3781e126e11de
--- /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 array(
+ 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 }
+