From 62daee6ba603ead8bfa0856336df3c8ebbeecba5 Mon Sep 17 00:00:00 2001 From: adev Date: Thu, 4 Oct 2018 21:55:27 +0200 Subject: [PATCH 1/2] [FrameworkBundle] Add debug:autoconfigure command Close #26295 --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Command/DebugAutoconfigurationCommand.php | 169 ++++++++++++++++++ .../FrameworkExtension.php | 4 +- .../Resources/config/console.xml | 4 + .../Autoconfiguration/Bindings.php | 15 ++ .../Autoconfiguration/MethodCalls.php | 14 ++ .../Autoconfiguration/TagsAttributes.php | 7 + .../DebugAutoconfigurationBundle.php | 9 + .../DebugAutoconfigurationExtension.php | 31 ++++ .../DebugAutoconfigurationCommandTest.php | 145 +++++++++++++++ .../app/DebugAutoconfiguration/bundles.php | 18 ++ .../app/DebugAutoconfiguration/config.yml | 3 + 12 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/Bindings.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/MethodCalls.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/Autoconfiguration/TagsAttributes.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/DebugAutoconfigurationBundle.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DebugAutoconfigurationBundle/DependencyInjection/DebugAutoconfigurationExtension.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutoconfigurationCommandTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/bundles.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/DebugAutoconfiguration/config.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 2da2363dab205..1651cfab03182 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -91,6 +91,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 09d141b2d234f..0433f00d8148f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -428,7 +428,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 6333f2d3cd0df..9f768f1b879b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -74,6 +74,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 } + From c876203e46b491207ebdf4bdbdbb305b3e7bec8b Mon Sep 17 00:00:00 2001 From: Ahmed TAILOULOUTE Date: Thu, 6 Feb 2020 20:32:36 +0100 Subject: [PATCH 2/2] WIP: Add debug:autoconfigure command --- .../Command/DebugAutoconfigurationCommand.php | 122 +++++++----------- .../DependencyInjection/Dumper/XmlDumper.php | 43 ++++++ .../Loader/XmlFileLoader.php | 48 +++++++ .../schema/dic/services/services-1.0.xsd | 23 ++++ 4 files changed, 162 insertions(+), 74 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php index 6c6bf58ce9a1c..b6b9d2770b858 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutoconfigurationCommand.php @@ -29,7 +29,7 @@ * * @internal */ -final class DebugAutoconfigurationCommand extends Command +final class DebugAutoconfigurationCommand extends ContainerDebugCommand { protected static $defaultName = 'debug:autoconfiguration'; @@ -45,8 +45,7 @@ protected function configure() ]) ->setDescription('Displays current autoconfiguration for an application') ->setHelp(<<<'EOF' -The %command.name% command displays all services that -are autoconfigured: +The %command.name% command displays all services that are autoconfigured: php %command.full_name% @@ -62,108 +61,83 @@ protected function configure() /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); - $autoconfiguredInstanceofItems = $this->getContainerBuilder()->getAutoconfiguredInstanceof(); + $definitions = $this->getContainerBuilder()->getAutoconfiguredInstanceof(); + ksort($definitions, SORT_NATURAL); if ($search = $input->getArgument('search')) { - $autoconfiguredInstanceofItems = array_filter($autoconfiguredInstanceofItems, function ($key) use ($search) { + $definitions = array_filter($definitions, function ($key) use ($search) { return false !== stripos(str_replace('\\', '', $key), $search); }, ARRAY_FILTER_USE_KEY); - if (!$autoconfiguredInstanceofItems) { + if (0 === \count($definitions)) { $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(); + $name = $this->findProperInterfaceName(array_keys($definitions), $input, $io, $search); + /** @var ChildDefinition $definition */ + $definition = $definitions[$name]; - /** @var ChildDefinition $autoconfiguredInstanceofItem */ - foreach ($autoconfiguredInstanceofItems as $key => $autoconfiguredInstanceofItem) { + $io->title(sprintf('Information for Interface/Class "%s"', $name)); + $tableHeaders = ['Option', 'Value']; $tableRows = []; - foreach ($autoconfiguredInstanceofItem->getTags() as $tag => $tagAttributes) { - $tableRows[] = ['Tag', $tag]; - if ($tagAttributes !== [[]]) { - $tableRows[] = ['Tag attribute', $this->dumpTagAttribute($tagAttributes)]; + $tagInformation = []; + foreach ($definition->getTags() as $tagName => $tagData) { + foreach ($tagData as $tagParameters) { + $parameters = array_map(function ($key, $value) { + return sprintf('%s: %s', $key, $value); + }, array_keys($tagParameters), array_values($tagParameters)); + $parameters = implode(', ', $parameters); + + if ('' === $parameters) { + $tagInformation[] = sprintf('%s', $tagName); + } else { + $tagInformation[] = sprintf('%s (%s)', $tagName, $parameters); + } } } + $tableRows[] = ['Tags', implode("\n", $tagInformation)]; - if ($autoconfiguredInstanceofItem->getMethodCalls()) { - $tableRows[] = ['Method call', $this->dumpMethodCall($autoconfiguredInstanceofItem)]; - } - - if ($autoconfiguredInstanceofItem->getBindings()) { - $tableRows[] = ['Bindings', $this->dumpBindings($autoconfiguredInstanceofItem)]; + $calls = $definition->getMethodCalls(); + if (\count($calls) > 0) { + $callInformation = []; + foreach ($calls as $call) { + $callInformation[] = $call[0]; + } + $tableRows[] = ['Calls', implode(', ', $callInformation)]; } - $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); + $io->table($tableHeaders, $tableRows); + } else { + $io->table(['Interface/Class'], array_map(static function ($interface) { + return [$interface]; + }, array_keys($definitions))); } - $tagContainerBuilder->addDefinitions([$autoconfiguredInstanceofItem]); - $dumper = new YamlDumper($tagContainerBuilder); - preg_match('/calls\:\n((?: +- .+\n)+)/', $dumper->dump(), $matches); + $io->newLine(); - return preg_replace('/^\s+/m', '', $matches[1]); + return 0; } - private function dumpBindings(ChildDefinition $autoconfiguredInstanceofItem) + private function findProperInterfaceName(array $list, InputInterface $input, SymfonyStyle $io, string $name): string { - $tagContainerBuilder = new ContainerBuilder(); - foreach ($tagContainerBuilder->getServiceIds() as $serviceId) { - $tagContainerBuilder->removeDefinition($serviceId); - $tagContainerBuilder->removeAlias($serviceId); - } + $name = ltrim($name, '\\'); - $dumper = new YamlDumper($tagContainerBuilder); - foreach ($autoconfiguredInstanceofItem->getBindings() as $bindingKey => $bindingValue) { - $tagContainerBuilder->setParameter($bindingKey, $bindingValue->getValues()[0]); + if (\in_array($name, $list, true)) { + return $name; } - 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); - } + if (1 === \count($list)) { + return $list[0]; + } - 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; + return $io->choice('Select one of the following interfaces to display its information', $list); } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index 6f7d918d26af4..768832d08a296 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -53,6 +54,7 @@ public function dump(array $options = []) $this->addParameters($container); $this->addServices($container); + $this->addAutoconfiguredInstanceof($container); $this->document->appendChild($container); $xml = $this->document->saveXML(); @@ -337,6 +339,47 @@ private function convertParameters(array $parameters, string $type, \DOMElement } } + private function addAutoconfiguredInstanceof(\DOMElement $parent) + { + $childDefinitions = $this->container->getAutoconfiguredInstanceof(); + + if (!$childDefinitions) { + return; + } + + $autoconfiguredInstanceOf = $this->document->createElement('autoconfigured-instanceof'); + + foreach ($childDefinitions as $id => $definition) { + $this->addAutoconfiguredInstanceofItem($definition, $id, $autoconfiguredInstanceOf); + } + +// dump($this->container); +// die; + + $parent->appendChild($autoconfiguredInstanceOf); + } + + private function addAutoconfiguredInstanceofItem(ChildDefinition $definition, string $id, \DOMElement $parent) + { + $item = $this->document->createElement('autoconfigured-instanceof-item'); + $item->setAttribute('id', $id); + + foreach ($definition->getTags() as $name => $tags) { + foreach ($tags as $attributes) { + $tag = $this->document->createElement('tag'); + $tag->setAttribute('name', $name); + foreach ($attributes as $key => $value) { + $tag->setAttribute($key, $value); + } + $item->appendChild($tag); + } + } + + $this->addMethodCalls($definition->getMethodCalls(), $item); + + $parent->appendChild($item); + } + /** * Escapes arguments. */ diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 6cad9453048e7..7e10b500a3ad4 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -71,6 +71,9 @@ public function load($resource, string $type = null) $this->instanceof = []; $this->registerAliasesForSinglyImplementedInterfaces(); } + + // autoconfiguredInstanceof + $this->parseAutoconfiguredInstanceOf($xml, $path); } /** @@ -112,6 +115,51 @@ private function parseImports(\DOMDocument $xml, string $file) } } + private function parseAutoconfiguredInstanceOf(\DOMDocument $xml, string $file) + { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + if (false === $autoconfiguredInstanceof = $xpath->query(('//container:autoconfigured-instanceof/container:autoconfigured-instanceof-item'))) { + return; + } + + foreach ($autoconfiguredInstanceof as $item) { + $definition = $this->container->registerForAutoconfiguration($item->getAttribute('id')); + + foreach ($this->getChildren($item, 'call') as $call) { + $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), XmlUtils::phpize($call->getAttribute('returns-clone'))); + } + + $tags = $this->getChildren($item, 'tag'); + + if (!empty($defaults['tags'])) { + $tags = array_merge($tags, $defaults['tags']); + } + + foreach ($tags as $tag) { + $parameters = []; + foreach ($tag->attributes as $name => $node) { + if ('name' === $name) { + continue; + } + + if (false !== strpos($name, '-') && false === strpos($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) { + $parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue); + } + // keep not normalized key + $parameters[$name] = XmlUtils::phpize($node->nodeValue); + } + + if ('' === $tag->getAttribute('name')) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', (string) $item->getAttribute('id'), $file)); + } + + $definition->addTag($tag->getAttribute('name'), $parameters); + } + } + } + private function parseDefinitions(\DOMDocument $xml, string $file, array $defaults) { $xpath = new \DOMXPath($xml); diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index d2c81bcf311c7..c504e36acabe0 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -37,6 +37,10 @@ + + + + @@ -60,6 +64,25 @@ + + + + + + + + + + + + + + + + +