ContainerDebugCommand.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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\Helper\DescriptorHelper;
  12. use Symfony\Component\Console\Command\Command;
  13. use Symfony\Component\Console\Exception\InvalidArgumentException;
  14. use Symfony\Component\Console\Input\InputArgument;
  15. use Symfony\Component\Console\Input\InputInterface;
  16. use Symfony\Component\Console\Input\InputOption;
  17. use Symfony\Component\Console\Output\OutputInterface;
  18. use Symfony\Component\Console\Style\SymfonyStyle;
  19. use Symfony\Component\DependencyInjection\ContainerBuilder;
  20. use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
  21. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  22. /**
  23. * A console command for retrieving information about services.
  24. *
  25. * @author Ryan Weaver <ryan@thatsquality.com>
  26. *
  27. * @internal
  28. */
  29. class ContainerDebugCommand extends Command
  30. {
  31. use BuildDebugContainerTrait;
  32. protected static $defaultName = 'debug:container';
  33. /**
  34. * {@inheritdoc}
  35. */
  36. protected function configure()
  37. {
  38. $this
  39. ->setDefinition([
  40. new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'),
  41. new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Show arguments in services'),
  42. new InputOption('show-hidden', null, InputOption::VALUE_NONE, 'Show hidden (internal) services'),
  43. new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Show all services with a specific tag'),
  44. new InputOption('tags', null, InputOption::VALUE_NONE, 'Display tagged services for an application'),
  45. new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Display a specific parameter for an application'),
  46. new InputOption('parameters', null, InputOption::VALUE_NONE, 'Display parameters for an application'),
  47. new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'),
  48. new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'),
  49. new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'),
  50. new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
  51. new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
  52. new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'),
  53. ])
  54. ->setDescription('Display current services for an application')
  55. ->setHelp(<<<'EOF'
  56. The <info>%command.name%</info> command displays all configured <comment>public</comment> services:
  57. <info>php %command.full_name%</info>
  58. To see deprecations generated during container compilation and cache warmup, use the <info>--deprecations</info> option:
  59. <info>php %command.full_name% --deprecations</info>
  60. To get specific information about a service, specify its name:
  61. <info>php %command.full_name% validator</info>
  62. To get specific information about a service including all its arguments, use the <info>--show-arguments</info> flag:
  63. <info>php %command.full_name% validator --show-arguments</info>
  64. To see available types that can be used for autowiring, use the <info>--types</info> flag:
  65. <info>php %command.full_name% --types</info>
  66. To see environment variables used by the container, use the <info>--env-vars</info> flag:
  67. <info>php %command.full_name% --env-vars</info>
  68. Display a specific environment variable by specifying its name with the <info>--env-var</info> option:
  69. <info>php %command.full_name% --env-var=APP_ENV</info>
  70. Use the --tags option to display tagged <comment>public</comment> services grouped by tag:
  71. <info>php %command.full_name% --tags</info>
  72. Find all services with a specific tag by specifying the tag name with the <info>--tag</info> option:
  73. <info>php %command.full_name% --tag=form.type</info>
  74. Use the <info>--parameters</info> option to display all parameters:
  75. <info>php %command.full_name% --parameters</info>
  76. Display a specific parameter by specifying its name with the <info>--parameter</info> option:
  77. <info>php %command.full_name% --parameter=kernel.debug</info>
  78. By default, internal services are hidden. You can display them
  79. using the <info>--show-hidden</info> flag:
  80. <info>php %command.full_name% --show-hidden</info>
  81. EOF
  82. )
  83. ;
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. protected function execute(InputInterface $input, OutputInterface $output): int
  89. {
  90. $io = new SymfonyStyle($input, $output);
  91. $errorIo = $io->getErrorStyle();
  92. $this->validateInput($input);
  93. $object = $this->getContainerBuilder();
  94. if ($input->getOption('env-vars')) {
  95. $options = ['env-vars' => true];
  96. } elseif ($envVar = $input->getOption('env-var')) {
  97. $options = ['env-vars' => true, 'name' => $envVar];
  98. } elseif ($input->getOption('types')) {
  99. $options = [];
  100. $options['filter'] = [$this, 'filterToServiceTypes'];
  101. } elseif ($input->getOption('parameters')) {
  102. $parameters = [];
  103. foreach ($object->getParameterBag()->all() as $k => $v) {
  104. $parameters[$k] = $object->resolveEnvPlaceholders($v);
  105. }
  106. $object = new ParameterBag($parameters);
  107. $options = [];
  108. } elseif ($parameter = $input->getOption('parameter')) {
  109. $options = ['parameter' => $parameter];
  110. } elseif ($input->getOption('tags')) {
  111. $options = ['group_by' => 'tags'];
  112. } elseif ($tag = $input->getOption('tag')) {
  113. $options = ['tag' => $tag];
  114. } elseif ($name = $input->getArgument('name')) {
  115. $name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden'));
  116. $options = ['id' => $name];
  117. } elseif ($input->getOption('deprecations')) {
  118. $options = ['deprecations' => true];
  119. } else {
  120. $options = [];
  121. }
  122. $helper = new DescriptorHelper();
  123. $options['format'] = $input->getOption('format');
  124. $options['show_arguments'] = $input->getOption('show-arguments');
  125. $options['show_hidden'] = $input->getOption('show-hidden');
  126. $options['raw_text'] = $input->getOption('raw');
  127. $options['output'] = $io;
  128. $options['is_debug'] = $this->getApplication()->getKernel()->isDebug();
  129. try {
  130. $helper->describe($io, $object, $options);
  131. if (isset($options['id']) && isset($this->getApplication()->getKernel()->getContainer()->getRemovedIds()[$options['id']])) {
  132. $errorIo->note(sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id']));
  133. }
  134. } catch (ServiceNotFoundException $e) {
  135. if ('' !== $e->getId() && '@' === $e->getId()[0]) {
  136. throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]);
  137. }
  138. throw $e;
  139. }
  140. if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && !$input->getOption('env-vars') && !$input->getOption('env-var') && $input->isInteractive()) {
  141. if ($input->getOption('tags')) {
  142. $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>)');
  143. } elseif ($input->getOption('parameters')) {
  144. $errorIo->comment('To search for a specific parameter, re-run this command with a search term. (e.g. <comment>debug:container --parameter=kernel.debug</comment>)');
  145. } elseif (!$input->getOption('deprecations')) {
  146. $errorIo->comment('To search for a specific service, re-run this command with a search term. (e.g. <comment>debug:container log</comment>)');
  147. }
  148. }
  149. return 0;
  150. }
  151. /**
  152. * Validates input arguments and options.
  153. *
  154. * @throws \InvalidArgumentException
  155. */
  156. protected function validateInput(InputInterface $input)
  157. {
  158. $options = ['tags', 'tag', 'parameters', 'parameter'];
  159. $optionsCount = 0;
  160. foreach ($options as $option) {
  161. if ($input->getOption($option)) {
  162. ++$optionsCount;
  163. }
  164. }
  165. $name = $input->getArgument('name');
  166. if ((null !== $name) && ($optionsCount > 0)) {
  167. throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined with the service name argument.');
  168. } elseif ((null === $name) && $optionsCount > 1) {
  169. throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined together.');
  170. }
  171. }
  172. private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden): string
  173. {
  174. $name = ltrim($name, '\\');
  175. if ($builder->has($name) || !$input->isInteractive()) {
  176. return $name;
  177. }
  178. $matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden);
  179. if (empty($matchingServices)) {
  180. throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name));
  181. }
  182. if (1 === \count($matchingServices)) {
  183. return $matchingServices[0];
  184. }
  185. return $io->choice('Select one of the following services to display its information', $matchingServices);
  186. }
  187. private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden): array
  188. {
  189. $serviceIds = $builder->getServiceIds();
  190. $foundServiceIds = $foundServiceIdsIgnoringBackslashes = [];
  191. foreach ($serviceIds as $serviceId) {
  192. if (!$showHidden && 0 === strpos($serviceId, '.')) {
  193. continue;
  194. }
  195. if (false !== stripos(str_replace('\\', '', $serviceId), $name)) {
  196. $foundServiceIdsIgnoringBackslashes[] = $serviceId;
  197. }
  198. if (false !== stripos($serviceId, $name)) {
  199. $foundServiceIds[] = $serviceId;
  200. }
  201. }
  202. return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes;
  203. }
  204. /**
  205. * @internal
  206. */
  207. public function filterToServiceTypes(string $serviceId): bool
  208. {
  209. // filter out things that could not be valid class names
  210. if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^(?&V)(?:\\\\(?&V))*+(?: \$(?&V))?$/', $serviceId)) {
  211. return false;
  212. }
  213. // if the id has a \, assume it is a class
  214. if (false !== strpos($serviceId, '\\')) {
  215. return true;
  216. }
  217. return class_exists($serviceId) || interface_exists($serviceId, false);
  218. }
  219. }