DebugCommand.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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\Bridge\Twig\Command;
  11. use Symfony\Component\Console\Command\Command;
  12. use Symfony\Component\Console\Input\InputArgument;
  13. use Symfony\Component\Console\Input\InputOption;
  14. use Symfony\Component\Console\Input\InputInterface;
  15. use Symfony\Component\Console\Output\OutputInterface;
  16. use Symfony\Component\Console\Style\SymfonyStyle;
  17. use Twig\Environment;
  18. /**
  19. * Lists twig functions, filters, globals and tests present in the current project.
  20. *
  21. * @author Jordi Boggiano <j.boggiano@seld.be>
  22. */
  23. class DebugCommand extends Command
  24. {
  25. private $twig;
  26. /**
  27. * {@inheritdoc}
  28. */
  29. public function __construct($name = 'debug:twig')
  30. {
  31. parent::__construct($name);
  32. }
  33. public function setTwigEnvironment(Environment $twig)
  34. {
  35. $this->twig = $twig;
  36. }
  37. /**
  38. * @return Environment $twig
  39. */
  40. protected function getTwigEnvironment()
  41. {
  42. return $this->twig;
  43. }
  44. protected function configure()
  45. {
  46. $this
  47. ->setDefinition(array(
  48. new InputArgument('filter', InputArgument::OPTIONAL, 'Show details for all entries matching this filter'),
  49. new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'),
  50. ))
  51. ->setDescription('Shows a list of twig functions, filters, globals and tests')
  52. ->setHelp(<<<'EOF'
  53. The <info>%command.name%</info> command outputs a list of twig functions,
  54. filters, globals and tests. Output can be filtered with an optional argument.
  55. <info>php %command.full_name%</info>
  56. The command lists all functions, filters, etc.
  57. <info>php %command.full_name% date</info>
  58. The command lists everything that contains the word date.
  59. <info>php %command.full_name% --format=json</info>
  60. The command lists everything in a machine readable json format.
  61. EOF
  62. )
  63. ;
  64. }
  65. protected function execute(InputInterface $input, OutputInterface $output)
  66. {
  67. $io = new SymfonyStyle($input, $output);
  68. $twig = $this->getTwigEnvironment();
  69. if (null === $twig) {
  70. throw new \RuntimeException('The Twig environment needs to be set.');
  71. }
  72. $types = array('functions', 'filters', 'tests', 'globals');
  73. if ($input->getOption('format') === 'json') {
  74. $data = array();
  75. foreach ($types as $type) {
  76. foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) {
  77. $data[$type][$name] = $this->getMetadata($type, $entity);
  78. }
  79. }
  80. $data['tests'] = array_keys($data['tests']);
  81. $io->writeln(json_encode($data));
  82. return 0;
  83. }
  84. $filter = $input->getArgument('filter');
  85. foreach ($types as $index => $type) {
  86. $items = array();
  87. foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) {
  88. if (!$filter || false !== strpos($name, $filter)) {
  89. $items[$name] = $name.$this->getPrettyMetadata($type, $entity);
  90. }
  91. }
  92. if (!$items) {
  93. continue;
  94. }
  95. $io->section(ucfirst($type));
  96. ksort($items);
  97. $io->listing($items);
  98. }
  99. return 0;
  100. }
  101. private function getMetadata($type, $entity)
  102. {
  103. if ($type === 'globals') {
  104. return $entity;
  105. }
  106. if ($type === 'tests') {
  107. return;
  108. }
  109. if ($type === 'functions' || $type === 'filters') {
  110. $cb = $entity->getCallable();
  111. if (null === $cb) {
  112. return;
  113. }
  114. if (is_array($cb)) {
  115. if (!method_exists($cb[0], $cb[1])) {
  116. return;
  117. }
  118. $refl = new \ReflectionMethod($cb[0], $cb[1]);
  119. } elseif (is_object($cb) && method_exists($cb, '__invoke')) {
  120. $refl = new \ReflectionMethod($cb, '__invoke');
  121. } elseif (function_exists($cb)) {
  122. $refl = new \ReflectionFunction($cb);
  123. } elseif (is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) {
  124. $refl = new \ReflectionMethod($m[1], $m[2]);
  125. } else {
  126. throw new \UnexpectedValueException('Unsupported callback type');
  127. }
  128. $args = $refl->getParameters();
  129. // filter out context/environment args
  130. if ($entity->needsEnvironment()) {
  131. array_shift($args);
  132. }
  133. if ($entity->needsContext()) {
  134. array_shift($args);
  135. }
  136. if ($type === 'filters') {
  137. // remove the value the filter is applied on
  138. array_shift($args);
  139. }
  140. // format args
  141. $args = array_map(function ($param) {
  142. if ($param->isDefaultValueAvailable()) {
  143. return $param->getName().' = '.json_encode($param->getDefaultValue());
  144. }
  145. return $param->getName();
  146. }, $args);
  147. return $args;
  148. }
  149. }
  150. private function getPrettyMetadata($type, $entity)
  151. {
  152. if ($type === 'tests') {
  153. return '';
  154. }
  155. try {
  156. $meta = $this->getMetadata($type, $entity);
  157. if ($meta === null) {
  158. return '(unknown?)';
  159. }
  160. } catch (\UnexpectedValueException $e) {
  161. return ' <error>'.$e->getMessage().'</error>';
  162. }
  163. if ($type === 'globals') {
  164. if (is_object($meta)) {
  165. return ' = object('.get_class($meta).')';
  166. }
  167. return ' = '.substr(@json_encode($meta), 0, 50);
  168. }
  169. if ($type === 'functions') {
  170. return '('.implode(', ', $meta).')';
  171. }
  172. if ($type === 'filters') {
  173. return $meta ? '('.implode(', ', $meta).')' : '';
  174. }
  175. }
  176. }