PageRenderTime 42ms CodeModel.GetById 21ms RepoModel.GetById 2ms app.codeStats 0ms

/vendor/psy/psysh/src/Command/ShowCommand.php

https://gitlab.com/madwanz64/laravel
PHP | 299 lines | 222 code | 44 blank | 33 comment | 34 complexity | 38483b9e67494306593dec1072b402bc MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2022 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\Exception\RuntimeException;
  12. use Psy\Exception\UnexpectedTargetException;
  13. use Psy\Formatter\CodeFormatter;
  14. use Psy\Formatter\SignatureFormatter;
  15. use Psy\Input\CodeArgument;
  16. use Symfony\Component\Console\Formatter\OutputFormatter;
  17. use Symfony\Component\Console\Input\InputInterface;
  18. use Symfony\Component\Console\Input\InputOption;
  19. use Symfony\Component\Console\Output\OutputInterface;
  20. /**
  21. * Show the code for an object, class, constant, method or property.
  22. */
  23. class ShowCommand extends ReflectingCommand
  24. {
  25. private $lastException;
  26. private $lastExceptionIndex;
  27. /**
  28. * @param string|null $colorMode (deprecated and ignored)
  29. */
  30. public function __construct($colorMode = null)
  31. {
  32. parent::__construct();
  33. }
  34. /**
  35. * {@inheritdoc}
  36. */
  37. protected function configure()
  38. {
  39. $this
  40. ->setName('show')
  41. ->setDefinition([
  42. new CodeArgument('target', CodeArgument::OPTIONAL, 'Function, class, instance, constant, method or property to show.'),
  43. new InputOption('ex', null, InputOption::VALUE_OPTIONAL, 'Show last exception context. Optionally specify a stack index.', 1),
  44. ])
  45. ->setDescription('Show the code for an object, class, constant, method or property.')
  46. ->setHelp(
  47. <<<HELP
  48. Show the code for an object, class, constant, method or property, or the context
  49. of the last exception.
  50. <return>cat --ex</return> defaults to showing the lines surrounding the location of the last
  51. exception. Invoking it more than once travels up the exception's stack trace,
  52. and providing a number shows the context of the given index of the trace.
  53. e.g.
  54. <return>>>> show \$myObject</return>
  55. <return>>>> show Psy\Shell::debug</return>
  56. <return>>>> show --ex</return>
  57. <return>>>> show --ex 3</return>
  58. HELP
  59. );
  60. }
  61. /**
  62. * {@inheritdoc}
  63. */
  64. protected function execute(InputInterface $input, OutputInterface $output)
  65. {
  66. // n.b. As far as I can tell, InputInterface doesn't want to tell me
  67. // whether an option with an optional value was actually passed. If you
  68. // call `$input->getOption('ex')`, it will return the default, both when
  69. // `--ex` is specified with no value, and when `--ex` isn't specified at
  70. // all.
  71. //
  72. // So we're doing something sneaky here. If we call `getOptions`, it'll
  73. // return the default value when `--ex` is not present, and `null` if
  74. // `--ex` is passed with no value. /shrug
  75. $opts = $input->getOptions();
  76. // Strict comparison to `1` (the default value) here, because `--ex 1`
  77. // will come in as `"1"`. Now we can tell the difference between
  78. // "no --ex present", because it's the integer 1, "--ex with no value",
  79. // because it's `null`, and "--ex 1", because it's the string "1".
  80. if ($opts['ex'] !== 1) {
  81. if ($input->getArgument('target')) {
  82. throw new \InvalidArgumentException('Too many arguments (supply either "target" or "--ex")');
  83. }
  84. $this->writeExceptionContext($input, $output);
  85. return 0;
  86. }
  87. if ($input->getArgument('target')) {
  88. $this->writeCodeContext($input, $output);
  89. return 0;
  90. }
  91. throw new RuntimeException('Not enough arguments (missing: "target")');
  92. }
  93. private function writeCodeContext(InputInterface $input, OutputInterface $output)
  94. {
  95. try {
  96. list($target, $reflector) = $this->getTargetAndReflector($input->getArgument('target'));
  97. } catch (UnexpectedTargetException $e) {
  98. // If we didn't get a target and Reflector, maybe we got a filename?
  99. $target = $e->getTarget();
  100. if (\is_string($target) && \is_file($target) && $code = @\file_get_contents($target)) {
  101. $file = \realpath($target);
  102. if ($file !== $this->context->get('__file')) {
  103. $this->context->setCommandScopeVariables([
  104. '__file' => $file,
  105. '__dir' => \dirname($file),
  106. ]);
  107. }
  108. $output->page(CodeFormatter::formatCode($code));
  109. return;
  110. } else {
  111. throw $e;
  112. }
  113. }
  114. // Set some magic local variables
  115. $this->setCommandScopeVariables($reflector);
  116. try {
  117. $output->page(CodeFormatter::format($reflector));
  118. } catch (RuntimeException $e) {
  119. $output->writeln(SignatureFormatter::format($reflector));
  120. throw $e;
  121. }
  122. }
  123. private function writeExceptionContext(InputInterface $input, OutputInterface $output)
  124. {
  125. $exception = $this->context->getLastException();
  126. if ($exception !== $this->lastException) {
  127. $this->lastException = null;
  128. $this->lastExceptionIndex = null;
  129. }
  130. $opts = $input->getOptions();
  131. if ($opts['ex'] === null) {
  132. if ($this->lastException && $this->lastExceptionIndex !== null) {
  133. $index = $this->lastExceptionIndex + 1;
  134. } else {
  135. $index = 0;
  136. }
  137. } else {
  138. $index = \max(0, (int) $input->getOption('ex') - 1);
  139. }
  140. $trace = $exception->getTrace();
  141. \array_unshift($trace, [
  142. 'file' => $exception->getFile(),
  143. 'line' => $exception->getLine(),
  144. ]);
  145. if ($index >= \count($trace)) {
  146. $index = 0;
  147. }
  148. $this->lastException = $exception;
  149. $this->lastExceptionIndex = $index;
  150. $output->writeln($this->getApplication()->formatException($exception));
  151. $output->writeln('--');
  152. $this->writeTraceLine($output, $trace, $index);
  153. $this->writeTraceCodeSnippet($output, $trace, $index);
  154. $this->setCommandScopeVariablesFromContext($trace[$index]);
  155. }
  156. private function writeTraceLine(OutputInterface $output, array $trace, $index)
  157. {
  158. $file = isset($trace[$index]['file']) ? $this->replaceCwd($trace[$index]['file']) : 'n/a';
  159. $line = isset($trace[$index]['line']) ? $trace[$index]['line'] : 'n/a';
  160. $output->writeln(\sprintf(
  161. 'From <info>%s:%d</info> at <strong>level %d</strong> of backtrace (of %d):',
  162. OutputFormatter::escape($file),
  163. OutputFormatter::escape($line),
  164. $index + 1,
  165. \count($trace)
  166. ));
  167. }
  168. private function replaceCwd(string $file): string
  169. {
  170. if ($cwd = \getcwd()) {
  171. $cwd = \rtrim($cwd, \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
  172. }
  173. if ($cwd === false) {
  174. return $file;
  175. } else {
  176. return \preg_replace('/^'.\preg_quote($cwd, '/').'/', '', $file);
  177. }
  178. }
  179. private function writeTraceCodeSnippet(OutputInterface $output, array $trace, $index)
  180. {
  181. if (!isset($trace[$index]['file'])) {
  182. return;
  183. }
  184. $file = $trace[$index]['file'];
  185. if ($fileAndLine = $this->extractEvalFileAndLine($file)) {
  186. list($file, $line) = $fileAndLine;
  187. } else {
  188. if (!isset($trace[$index]['line'])) {
  189. return;
  190. }
  191. $line = $trace[$index]['line'];
  192. }
  193. if (\is_file($file)) {
  194. $code = @\file_get_contents($file);
  195. }
  196. if (empty($code)) {
  197. return;
  198. }
  199. $startLine = \max($line - 5, 0);
  200. $endLine = $line + 5;
  201. $output->write(CodeFormatter::formatCode($code, $startLine, $endLine, $line), false);
  202. }
  203. private function setCommandScopeVariablesFromContext(array $context)
  204. {
  205. $vars = [];
  206. if (isset($context['class'])) {
  207. $vars['__class'] = $context['class'];
  208. if (isset($context['function'])) {
  209. $vars['__method'] = $context['function'];
  210. }
  211. try {
  212. $refl = new \ReflectionClass($context['class']);
  213. if ($namespace = $refl->getNamespaceName()) {
  214. $vars['__namespace'] = $namespace;
  215. }
  216. } catch (\Throwable $e) {
  217. // oh well
  218. }
  219. } elseif (isset($context['function'])) {
  220. $vars['__function'] = $context['function'];
  221. try {
  222. $refl = new \ReflectionFunction($context['function']);
  223. if ($namespace = $refl->getNamespaceName()) {
  224. $vars['__namespace'] = $namespace;
  225. }
  226. } catch (\Throwable $e) {
  227. // oh well
  228. }
  229. }
  230. if (isset($context['file'])) {
  231. $file = $context['file'];
  232. if ($fileAndLine = $this->extractEvalFileAndLine($file)) {
  233. list($file, $line) = $fileAndLine;
  234. } elseif (isset($context['line'])) {
  235. $line = $context['line'];
  236. }
  237. if (\is_file($file)) {
  238. $vars['__file'] = $file;
  239. if (isset($line)) {
  240. $vars['__line'] = $line;
  241. }
  242. $vars['__dir'] = \dirname($file);
  243. }
  244. }
  245. $this->context->setCommandScopeVariables($vars);
  246. }
  247. private function extractEvalFileAndLine(string $file)
  248. {
  249. if (\preg_match('/(.*)\\((\\d+)\\) : eval\\(\\)\'d code$/', $file, $matches)) {
  250. return [$matches[1], $matches[2]];
  251. }
  252. }
  253. }