DebugAutowiringCommand.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bundle\FrameworkBundle\Command;
  11. use Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor;
  12. use Symfony\Component\Console\Formatter\OutputFormatterStyle;
  13. use Symfony\Component\Console\Input\InputArgument;
  14. use Symfony\Component\Console\Input\InputInterface;
  15. use Symfony\Component\Console\Input\InputOption;
  16. use Symfony\Component\Console\Output\OutputInterface;
  17. use Symfony\Component\Console\Style\SymfonyStyle;
  18. use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
  19. /**
  20. * A console command for autowiring information.
  21. *
  22. * @author Ryan Weaver <ryan@knpuniversity.com>
  23. *
  24. * @internal
  25. */
  26. class DebugAutowiringCommand extends ContainerDebugCommand
  27. {
  28. protected static $defaultName = 'debug:autowiring';
  29. private $supportsHref;
  30. private $fileLinkFormatter;
  31. public function __construct(string $name = null, FileLinkFormatter $fileLinkFormatter = null)
  32. {
  33. $this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref');
  34. $this->fileLinkFormatter = $fileLinkFormatter;
  35. parent::__construct($name);
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. protected function configure()
  41. {
  42. $this
  43. ->setDefinition([
  44. new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'),
  45. new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'),
  46. ])
  47. ->setDescription('List classes/interfaces you can use for autowiring')
  48. ->setHelp(<<<'EOF'
  49. The <info>%command.name%</info> command displays the classes and interfaces that
  50. you can use as type-hints for autowiring:
  51. <info>php %command.full_name%</info>
  52. You can also pass a search term to filter the list:
  53. <info>php %command.full_name% log</info>
  54. EOF
  55. )
  56. ;
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. protected function execute(InputInterface $input, OutputInterface $output): int
  62. {
  63. $io = new SymfonyStyle($input, $output);
  64. $errorIo = $io->getErrorStyle();
  65. $builder = $this->getContainerBuilder();
  66. $serviceIds = $builder->getServiceIds();
  67. $serviceIds = array_filter($serviceIds, [$this, 'filterToServiceTypes']);
  68. if ($search = $input->getArgument('search')) {
  69. $serviceIds = array_filter($serviceIds, function ($serviceId) use ($search) {
  70. return false !== stripos(str_replace('\\', '', $serviceId), $search) && 0 !== strpos($serviceId, '.');
  71. });
  72. if (empty($serviceIds)) {
  73. $errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search));
  74. return 1;
  75. }
  76. }
  77. uasort($serviceIds, 'strnatcmp');
  78. $io->title('Autowirable Types');
  79. $io->text('The following classes & interfaces can be used as type-hints when autowiring:');
  80. if ($search) {
  81. $io->text(sprintf('(only showing classes/interfaces matching <comment>%s</comment>)', $search));
  82. }
  83. $hasAlias = [];
  84. $all = $input->getOption('all');
  85. $previousId = '-';
  86. $serviceIdsNb = 0;
  87. foreach ($serviceIds as $serviceId) {
  88. $text = [];
  89. $resolvedServiceId = $serviceId;
  90. if (0 !== strpos($serviceId, $previousId)) {
  91. $text[] = '';
  92. if ('' !== $description = Descriptor::getClassDescription($serviceId, $resolvedServiceId)) {
  93. if (isset($hasAlias[$serviceId])) {
  94. continue;
  95. }
  96. $text[] = $description;
  97. }
  98. $previousId = $serviceId.' $';
  99. }
  100. $serviceLine = sprintf('<fg=yellow>%s</>', $serviceId);
  101. if ($this->supportsHref && '' !== $fileLink = $this->getFileLink($serviceId)) {
  102. $serviceLine = sprintf('<fg=yellow;href=%s>%s</>', $fileLink, $serviceId);
  103. }
  104. if ($builder->hasAlias($serviceId)) {
  105. $hasAlias[$serviceId] = true;
  106. $serviceAlias = $builder->getAlias($serviceId);
  107. $serviceLine .= ' <fg=cyan>('.$serviceAlias.')</>';
  108. if ($serviceAlias->isDeprecated()) {
  109. $serviceLine .= ' - <fg=magenta>deprecated</>';
  110. }
  111. } elseif (!$all) {
  112. ++$serviceIdsNb;
  113. continue;
  114. }
  115. $text[] = $serviceLine;
  116. $io->text($text);
  117. }
  118. $io->newLine();
  119. if (0 < $serviceIdsNb) {
  120. $io->text(sprintf('%s more concrete service%s would be displayed when adding the "--all" option.', $serviceIdsNb, $serviceIdsNb > 1 ? 's' : ''));
  121. }
  122. if ($all) {
  123. $io->text('Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.');
  124. }
  125. $io->newLine();
  126. return 0;
  127. }
  128. private function getFileLink(string $class): string
  129. {
  130. if (null === $this->fileLinkFormatter
  131. || (null === $r = $this->getContainerBuilder()->getReflectionClass($class, false))) {
  132. return '';
  133. }
  134. return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine());
  135. }
  136. }