ListCommand.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2023 Justin Hileman
  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 Psy\Command;
  11. use Psy\Command\ListCommand\ClassConstantEnumerator;
  12. use Psy\Command\ListCommand\ClassEnumerator;
  13. use Psy\Command\ListCommand\ConstantEnumerator;
  14. use Psy\Command\ListCommand\FunctionEnumerator;
  15. use Psy\Command\ListCommand\GlobalVariableEnumerator;
  16. use Psy\Command\ListCommand\MethodEnumerator;
  17. use Psy\Command\ListCommand\PropertyEnumerator;
  18. use Psy\Command\ListCommand\VariableEnumerator;
  19. use Psy\Exception\RuntimeException;
  20. use Psy\Input\CodeArgument;
  21. use Psy\Input\FilterOptions;
  22. use Psy\Output\ShellOutput;
  23. use Psy\VarDumper\Presenter;
  24. use Psy\VarDumper\PresenterAware;
  25. use Symfony\Component\Console\Formatter\OutputFormatter;
  26. use Symfony\Component\Console\Input\InputInterface;
  27. use Symfony\Component\Console\Input\InputOption;
  28. use Symfony\Component\Console\Output\OutputInterface;
  29. /**
  30. * List available local variables, object properties, etc.
  31. */
  32. class ListCommand extends ReflectingCommand implements PresenterAware
  33. {
  34. protected Presenter $presenter;
  35. protected array $enumerators;
  36. /**
  37. * PresenterAware interface.
  38. *
  39. * @param Presenter $presenter
  40. */
  41. public function setPresenter(Presenter $presenter)
  42. {
  43. $this->presenter = $presenter;
  44. }
  45. /**
  46. * {@inheritdoc}
  47. */
  48. protected function configure()
  49. {
  50. list($grep, $insensitive, $invert) = FilterOptions::getOptions();
  51. $this
  52. ->setName('ls')
  53. ->setAliases(['dir'])
  54. ->setDefinition([
  55. new CodeArgument('target', CodeArgument::OPTIONAL, 'A target class or object to list.'),
  56. new InputOption('vars', '', InputOption::VALUE_NONE, 'Display variables.'),
  57. new InputOption('constants', 'c', InputOption::VALUE_NONE, 'Display defined constants.'),
  58. new InputOption('functions', 'f', InputOption::VALUE_NONE, 'Display defined functions.'),
  59. new InputOption('classes', 'k', InputOption::VALUE_NONE, 'Display declared classes.'),
  60. new InputOption('interfaces', 'I', InputOption::VALUE_NONE, 'Display declared interfaces.'),
  61. new InputOption('traits', 't', InputOption::VALUE_NONE, 'Display declared traits.'),
  62. new InputOption('no-inherit', '', InputOption::VALUE_NONE, 'Exclude inherited methods, properties and constants.'),
  63. new InputOption('properties', 'p', InputOption::VALUE_NONE, 'Display class or object properties (public properties by default).'),
  64. new InputOption('methods', 'm', InputOption::VALUE_NONE, 'Display class or object methods (public methods by default).'),
  65. $grep,
  66. $insensitive,
  67. $invert,
  68. new InputOption('globals', 'g', InputOption::VALUE_NONE, 'Include global variables.'),
  69. new InputOption('internal', 'n', InputOption::VALUE_NONE, 'Limit to internal functions and classes.'),
  70. new InputOption('user', 'u', InputOption::VALUE_NONE, 'Limit to user-defined constants, functions and classes.'),
  71. new InputOption('category', 'C', InputOption::VALUE_REQUIRED, 'Limit to constants in a specific category (e.g. "date").'),
  72. new InputOption('all', 'a', InputOption::VALUE_NONE, 'Include private and protected methods and properties.'),
  73. new InputOption('long', 'l', InputOption::VALUE_NONE, 'List in long format: includes class names and method signatures.'),
  74. ])
  75. ->setDescription('List local, instance or class variables, methods and constants.')
  76. ->setHelp(
  77. <<<'HELP'
  78. List variables, constants, classes, interfaces, traits, functions, methods,
  79. and properties.
  80. Called without options, this will return a list of variables currently in scope.
  81. If a target object is provided, list properties, constants and methods of that
  82. target. If a class, interface or trait name is passed instead, list constants
  83. and methods on that class.
  84. e.g.
  85. <return>>>> ls</return>
  86. <return>>>> ls $foo</return>
  87. <return>>>> ls -k --grep mongo -i</return>
  88. <return>>>> ls -al ReflectionClass</return>
  89. <return>>>> ls --constants --category date</return>
  90. <return>>>> ls -l --functions --grep /^array_.*/</return>
  91. <return>>>> ls -l --properties new DateTime()</return>
  92. HELP
  93. );
  94. }
  95. /**
  96. * {@inheritdoc}
  97. *
  98. * @return int 0 if everything went fine, or an exit code
  99. */
  100. protected function execute(InputInterface $input, OutputInterface $output): int
  101. {
  102. $this->validateInput($input);
  103. $this->initEnumerators();
  104. $method = $input->getOption('long') ? 'writeLong' : 'write';
  105. if ($target = $input->getArgument('target')) {
  106. list($target, $reflector) = $this->getTargetAndReflector($target);
  107. } else {
  108. $reflector = null;
  109. }
  110. // @todo something cleaner than this :-/
  111. if ($output instanceof ShellOutput && $input->getOption('long')) {
  112. $output->startPaging();
  113. }
  114. foreach ($this->enumerators as $enumerator) {
  115. $this->$method($output, $enumerator->enumerate($input, $reflector, $target));
  116. }
  117. if ($output instanceof ShellOutput && $input->getOption('long')) {
  118. $output->stopPaging();
  119. }
  120. // Set some magic local variables
  121. if ($reflector !== null) {
  122. $this->setCommandScopeVariables($reflector);
  123. }
  124. return 0;
  125. }
  126. /**
  127. * Initialize Enumerators.
  128. */
  129. protected function initEnumerators()
  130. {
  131. if (!isset($this->enumerators)) {
  132. $mgr = $this->presenter;
  133. $this->enumerators = [
  134. new ClassConstantEnumerator($mgr),
  135. new ClassEnumerator($mgr),
  136. new ConstantEnumerator($mgr),
  137. new FunctionEnumerator($mgr),
  138. new GlobalVariableEnumerator($mgr),
  139. new PropertyEnumerator($mgr),
  140. new MethodEnumerator($mgr),
  141. new VariableEnumerator($mgr, $this->context),
  142. ];
  143. }
  144. }
  145. /**
  146. * Write the list items to $output.
  147. *
  148. * @param OutputInterface $output
  149. * @param array $result List of enumerated items
  150. */
  151. protected function write(OutputInterface $output, array $result)
  152. {
  153. if (\count($result) === 0) {
  154. return;
  155. }
  156. foreach ($result as $label => $items) {
  157. $names = \array_map([$this, 'formatItemName'], $items);
  158. $output->writeln(\sprintf('<strong>%s</strong>: %s', $label, \implode(', ', $names)));
  159. }
  160. }
  161. /**
  162. * Write the list items to $output.
  163. *
  164. * Items are listed one per line, and include the item signature.
  165. *
  166. * @param OutputInterface $output
  167. * @param array $result List of enumerated items
  168. */
  169. protected function writeLong(OutputInterface $output, array $result)
  170. {
  171. if (\count($result) === 0) {
  172. return;
  173. }
  174. $table = $this->getTable($output);
  175. foreach ($result as $label => $items) {
  176. $output->writeln('');
  177. $output->writeln(\sprintf('<strong>%s:</strong>', $label));
  178. $table->setRows([]);
  179. foreach ($items as $item) {
  180. $table->addRow([$this->formatItemName($item), $item['value']]);
  181. }
  182. $table->render();
  183. }
  184. }
  185. /**
  186. * Format an item name given its visibility.
  187. *
  188. * @param array $item
  189. */
  190. private function formatItemName(array $item): string
  191. {
  192. return \sprintf('<%s>%s</%s>', $item['style'], OutputFormatter::escape($item['name']), $item['style']);
  193. }
  194. /**
  195. * Validate that input options make sense, provide defaults when called without options.
  196. *
  197. * @throws RuntimeException if options are inconsistent
  198. *
  199. * @param InputInterface $input
  200. */
  201. private function validateInput(InputInterface $input)
  202. {
  203. if (!$input->getArgument('target')) {
  204. // if no target is passed, there can be no properties or methods
  205. foreach (['properties', 'methods', 'no-inherit'] as $option) {
  206. if ($input->getOption($option)) {
  207. throw new RuntimeException('--'.$option.' does not make sense without a specified target');
  208. }
  209. }
  210. foreach (['globals', 'vars', 'constants', 'functions', 'classes', 'interfaces', 'traits'] as $option) {
  211. if ($input->getOption($option)) {
  212. return;
  213. }
  214. }
  215. // default to --vars if no other options are passed
  216. $input->setOption('vars', true);
  217. } else {
  218. // if a target is passed, classes, functions, etc don't make sense
  219. foreach (['vars', 'globals'] as $option) {
  220. if ($input->getOption($option)) {
  221. throw new RuntimeException('--'.$option.' does not make sense with a specified target');
  222. }
  223. }
  224. // @todo ensure that 'functions', 'classes', 'interfaces', 'traits' only accept namespace target?
  225. foreach (['constants', 'properties', 'methods', 'functions', 'classes', 'interfaces', 'traits'] as $option) {
  226. if ($input->getOption($option)) {
  227. return;
  228. }
  229. }
  230. // default to --constants --properties --methods if no other options are passed
  231. $input->setOption('constants', true);
  232. $input->setOption('properties', true);
  233. $input->setOption('methods', true);
  234. }
  235. }
  236. }