TextDescriptor.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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\Console\Descriptor;
  11. use Symfony\Component\Console\Helper\Table;
  12. use Symfony\Component\Console\Style\SymfonyStyle;
  13. use Symfony\Component\DependencyInjection\Alias;
  14. use Symfony\Component\DependencyInjection\ContainerBuilder;
  15. use Symfony\Component\DependencyInjection\Definition;
  16. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  17. use Symfony\Component\DependencyInjection\Reference;
  18. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  19. use Symfony\Component\Routing\Route;
  20. use Symfony\Component\Routing\RouteCollection;
  21. /**
  22. * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  23. *
  24. * @internal
  25. */
  26. class TextDescriptor extends Descriptor
  27. {
  28. /**
  29. * {@inheritdoc}
  30. */
  31. protected function describeRouteCollection(RouteCollection $routes, array $options = array())
  32. {
  33. $showControllers = isset($options['show_controllers']) && $options['show_controllers'];
  34. $tableHeaders = array('Name', 'Method', 'Scheme', 'Host', 'Path');
  35. if ($showControllers) {
  36. $tableHeaders[] = 'Controller';
  37. }
  38. $tableRows = array();
  39. foreach ($routes->all() as $name => $route) {
  40. $row = array(
  41. $name,
  42. $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY',
  43. $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY',
  44. '' !== $route->getHost() ? $route->getHost() : 'ANY',
  45. $route->getPath(),
  46. );
  47. if ($showControllers) {
  48. $controller = $route->getDefault('_controller');
  49. if ($controller instanceof \Closure) {
  50. $controller = 'Closure';
  51. } elseif (\is_object($controller)) {
  52. $controller = \get_class($controller);
  53. }
  54. $row[] = $controller;
  55. }
  56. $tableRows[] = $row;
  57. }
  58. if (isset($options['output'])) {
  59. $options['output']->table($tableHeaders, $tableRows);
  60. } else {
  61. $table = new Table($this->getOutput());
  62. $table->setHeaders($tableHeaders)->setRows($tableRows);
  63. $table->render();
  64. }
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. protected function describeRoute(Route $route, array $options = array())
  70. {
  71. $requirements = $route->getRequirements();
  72. unset($requirements['_scheme'], $requirements['_method']);
  73. $tableHeaders = array('Property', 'Value');
  74. $tableRows = array(
  75. array('Route Name', isset($options['name']) ? $options['name'] : ''),
  76. array('Path', $route->getPath()),
  77. array('Path Regex', $route->compile()->getRegex()),
  78. array('Host', ('' !== $route->getHost() ? $route->getHost() : 'ANY')),
  79. array('Host Regex', ('' !== $route->getHost() ? $route->compile()->getHostRegex() : '')),
  80. array('Scheme', ($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY')),
  81. array('Method', ($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')),
  82. array('Requirements', ($requirements ? $this->formatRouterConfig($requirements) : 'NO CUSTOM')),
  83. array('Class', \get_class($route)),
  84. array('Defaults', $this->formatRouterConfig($route->getDefaults())),
  85. array('Options', $this->formatRouterConfig($route->getOptions())),
  86. );
  87. $table = new Table($this->getOutput());
  88. $table->setHeaders($tableHeaders)->setRows($tableRows);
  89. $table->render();
  90. }
  91. /**
  92. * {@inheritdoc}
  93. */
  94. protected function describeContainerParameters(ParameterBag $parameters, array $options = array())
  95. {
  96. $tableHeaders = array('Parameter', 'Value');
  97. $tableRows = array();
  98. foreach ($this->sortParameters($parameters) as $parameter => $value) {
  99. $tableRows[] = array($parameter, $this->formatParameter($value));
  100. }
  101. $options['output']->title('Symfony Container Parameters');
  102. $options['output']->table($tableHeaders, $tableRows);
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. protected function describeContainerTags(ContainerBuilder $builder, array $options = array())
  108. {
  109. $showPrivate = isset($options['show_private']) && $options['show_private'];
  110. if ($showPrivate) {
  111. $options['output']->title('Symfony Container Public and Private Tags');
  112. } else {
  113. $options['output']->title('Symfony Container Public Tags');
  114. }
  115. foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) {
  116. $options['output']->section(sprintf('"%s" tag', $tag));
  117. $options['output']->listing(array_keys($definitions));
  118. }
  119. }
  120. /**
  121. * {@inheritdoc}
  122. */
  123. protected function describeContainerService($service, array $options = array())
  124. {
  125. if (!isset($options['id'])) {
  126. throw new \InvalidArgumentException('An "id" option must be provided.');
  127. }
  128. if ($service instanceof Alias) {
  129. $this->describeContainerAlias($service, $options);
  130. } elseif ($service instanceof Definition) {
  131. $this->describeContainerDefinition($service, $options);
  132. } else {
  133. $options['output']->title(sprintf('Information for Service "<info>%s</info>"', $options['id']));
  134. $options['output']->table(
  135. array('Service ID', 'Class'),
  136. array(
  137. array(isset($options['id']) ? $options['id'] : '-', \get_class($service)),
  138. )
  139. );
  140. }
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. protected function describeContainerServices(ContainerBuilder $builder, array $options = array())
  146. {
  147. $showPrivate = isset($options['show_private']) && $options['show_private'];
  148. $showTag = isset($options['tag']) ? $options['tag'] : null;
  149. if ($showPrivate) {
  150. $title = 'Symfony Container Public and Private Services';
  151. } else {
  152. $title = 'Symfony Container Public Services';
  153. }
  154. if ($showTag) {
  155. $title .= sprintf(' Tagged with "%s" Tag', $options['tag']);
  156. }
  157. $options['output']->title($title);
  158. $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds();
  159. $maxTags = array();
  160. foreach ($serviceIds as $key => $serviceId) {
  161. $definition = $this->resolveServiceDefinition($builder, $serviceId);
  162. if ($definition instanceof Definition) {
  163. // filter out private services unless shown explicitly
  164. if (!$showPrivate && !$definition->isPublic()) {
  165. unset($serviceIds[$key]);
  166. continue;
  167. }
  168. if ($showTag) {
  169. $tags = $definition->getTag($showTag);
  170. foreach ($tags as $tag) {
  171. foreach ($tag as $key => $value) {
  172. if (!isset($maxTags[$key])) {
  173. $maxTags[$key] = \strlen($key);
  174. }
  175. if (\strlen($value) > $maxTags[$key]) {
  176. $maxTags[$key] = \strlen($value);
  177. }
  178. }
  179. }
  180. }
  181. }
  182. }
  183. $tagsCount = \count($maxTags);
  184. $tagsNames = array_keys($maxTags);
  185. $tableHeaders = array_merge(array('Service ID'), $tagsNames, array('Class name'));
  186. $tableRows = array();
  187. foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
  188. $definition = $this->resolveServiceDefinition($builder, $serviceId);
  189. if ($definition instanceof Definition) {
  190. if ($showTag) {
  191. foreach ($definition->getTag($showTag) as $key => $tag) {
  192. $tagValues = array();
  193. foreach ($tagsNames as $tagName) {
  194. $tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : '';
  195. }
  196. if (0 === $key) {
  197. $tableRows[] = array_merge(array($serviceId), $tagValues, array($definition->getClass()));
  198. } else {
  199. $tableRows[] = array_merge(array(' "'), $tagValues, array(''));
  200. }
  201. }
  202. } else {
  203. $tableRows[] = array($serviceId, $definition->getClass());
  204. }
  205. } elseif ($definition instanceof Alias) {
  206. $alias = $definition;
  207. $tableRows[] = array_merge(array($serviceId, sprintf('alias for "%s"', $alias)), $tagsCount ? array_fill(0, $tagsCount, '') : array());
  208. } else {
  209. $tableRows[] = array_merge(array($serviceId, \get_class($definition)), $tagsCount ? array_fill(0, $tagsCount, '') : array());
  210. }
  211. }
  212. $options['output']->table($tableHeaders, $tableRows);
  213. }
  214. /**
  215. * {@inheritdoc}
  216. */
  217. protected function describeContainerDefinition(Definition $definition, array $options = array())
  218. {
  219. if (isset($options['id'])) {
  220. $options['output']->title(sprintf('Information for Service "<info>%s</info>"', $options['id']));
  221. }
  222. $tableHeaders = array('Option', 'Value');
  223. $tableRows[] = array('Service ID', isset($options['id']) ? $options['id'] : '-');
  224. $tableRows[] = array('Class', $definition->getClass() ?: '-');
  225. if ($tags = $definition->getTags()) {
  226. $tagInformation = '';
  227. foreach ($tags as $tagName => $tagData) {
  228. foreach ($tagData as $tagParameters) {
  229. $parameters = array_map(function ($key, $value) {
  230. return sprintf('<info>%s</info>: %s', $key, $value);
  231. }, array_keys($tagParameters), array_values($tagParameters));
  232. $parameters = implode(', ', $parameters);
  233. if ('' === $parameters) {
  234. $tagInformation .= sprintf('%s', $tagName);
  235. } else {
  236. $tagInformation .= sprintf('%s (%s)', $tagName, $parameters);
  237. }
  238. }
  239. }
  240. } else {
  241. $tagInformation = '-';
  242. }
  243. $tableRows[] = array('Tags', $tagInformation);
  244. $tableRows[] = array('Scope', $definition->getScope(false));
  245. $tableRows[] = array('Public', $definition->isPublic() ? 'yes' : 'no');
  246. $tableRows[] = array('Synthetic', $definition->isSynthetic() ? 'yes' : 'no');
  247. $tableRows[] = array('Lazy', $definition->isLazy() ? 'yes' : 'no');
  248. $tableRows[] = array('Synchronized', $definition->isSynchronized(false) ? 'yes' : 'no');
  249. $tableRows[] = array('Abstract', $definition->isAbstract() ? 'yes' : 'no');
  250. $tableRows[] = array('Autowired', $definition->isAutowired() ? 'yes' : 'no');
  251. $autowiringTypes = $definition->getAutowiringTypes();
  252. $tableRows[] = array('Autowiring Types', $autowiringTypes ? implode(', ', $autowiringTypes) : '-');
  253. if ($definition->getFile()) {
  254. $tableRows[] = array('Required File', $definition->getFile() ? $definition->getFile() : '-');
  255. }
  256. if ($definition->getFactoryClass(false)) {
  257. $tableRows[] = array('Factory Class', $definition->getFactoryClass(false));
  258. }
  259. if ($definition->getFactoryService(false)) {
  260. $tableRows[] = array('Factory Service', $definition->getFactoryService(false));
  261. }
  262. if ($definition->getFactoryMethod(false)) {
  263. $tableRows[] = array('Factory Method', $definition->getFactoryMethod(false));
  264. }
  265. if ($factory = $definition->getFactory()) {
  266. if (\is_array($factory)) {
  267. if ($factory[0] instanceof Reference) {
  268. $tableRows[] = array('Factory Service', $factory[0]);
  269. } elseif ($factory[0] instanceof Definition) {
  270. throw new \InvalidArgumentException('Factory is not describable.');
  271. } else {
  272. $tableRows[] = array('Factory Class', $factory[0]);
  273. }
  274. $tableRows[] = array('Factory Method', $factory[1]);
  275. } else {
  276. $tableRows[] = array('Factory Function', $factory);
  277. }
  278. }
  279. $options['output']->table($tableHeaders, $tableRows);
  280. }
  281. /**
  282. * {@inheritdoc}
  283. */
  284. protected function describeContainerAlias(Alias $alias, array $options = array())
  285. {
  286. $options['output']->comment(sprintf('This service is an alias for the service <info>%s</info>', (string) $alias));
  287. }
  288. /**
  289. * {@inheritdoc}
  290. */
  291. protected function describeContainerParameter($parameter, array $options = array())
  292. {
  293. $options['output']->table(
  294. array('Parameter', 'Value'),
  295. array(
  296. array($options['parameter'], $this->formatParameter($parameter),
  297. ),
  298. ));
  299. }
  300. /**
  301. * {@inheritdoc}
  302. */
  303. protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array())
  304. {
  305. $event = array_key_exists('event', $options) ? $options['event'] : null;
  306. if (null !== $event) {
  307. $title = sprintf('Registered Listeners for "%s" Event', $event);
  308. } else {
  309. $title = 'Registered Listeners Grouped by Event';
  310. }
  311. $options['output']->title($title);
  312. $registeredListeners = $eventDispatcher->getListeners($event);
  313. if (null !== $event) {
  314. $this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']);
  315. } else {
  316. ksort($registeredListeners);
  317. foreach ($registeredListeners as $eventListened => $eventListeners) {
  318. $options['output']->section(sprintf('"%s" event', $eventListened));
  319. $this->renderEventListenerTable($eventDispatcher, $eventListened, $eventListeners, $options['output']);
  320. }
  321. }
  322. }
  323. /**
  324. * {@inheritdoc}
  325. */
  326. protected function describeCallable($callable, array $options = array())
  327. {
  328. $this->writeText($this->formatCallable($callable), $options);
  329. }
  330. private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, $event, array $eventListeners, SymfonyStyle $io)
  331. {
  332. $tableHeaders = array('Order', 'Callable', 'Priority');
  333. $tableRows = array();
  334. $order = 1;
  335. foreach ($eventListeners as $order => $listener) {
  336. $tableRows[] = array(sprintf('#%d', $order + 1), $this->formatCallable($listener), $eventDispatcher->getListenerPriority($event, $listener));
  337. }
  338. $io->table($tableHeaders, $tableRows);
  339. }
  340. /**
  341. * @param array $config
  342. *
  343. * @return string
  344. */
  345. private function formatRouterConfig(array $config)
  346. {
  347. if (empty($config)) {
  348. return 'NONE';
  349. }
  350. ksort($config);
  351. $configAsString = '';
  352. foreach ($config as $key => $value) {
  353. $configAsString .= sprintf("\n%s: %s", $key, $this->formatValue($value));
  354. }
  355. return trim($configAsString);
  356. }
  357. /**
  358. * @param callable $callable
  359. *
  360. * @return string
  361. */
  362. private function formatCallable($callable)
  363. {
  364. if (\is_array($callable)) {
  365. if (\is_object($callable[0])) {
  366. return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]);
  367. }
  368. return sprintf('%s::%s()', $callable[0], $callable[1]);
  369. }
  370. if (\is_string($callable)) {
  371. return sprintf('%s()', $callable);
  372. }
  373. if ($callable instanceof \Closure) {
  374. return '\Closure()';
  375. }
  376. if (method_exists($callable, '__invoke')) {
  377. return sprintf('%s::__invoke()', \get_class($callable));
  378. }
  379. throw new \InvalidArgumentException('Callable is not describable.');
  380. }
  381. /**
  382. * @param string $content
  383. * @param array $options
  384. */
  385. private function writeText($content, array $options = array())
  386. {
  387. $this->write(
  388. isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
  389. isset($options['raw_output']) ? !$options['raw_output'] : true
  390. );
  391. }
  392. }