Chris@0: filter = new FilterOptions(); Chris@0: Chris@0: parent::__construct($name); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function configure() Chris@0: { Chris@0: list($grep, $insensitive, $invert) = FilterOptions::getOptions(); Chris@0: Chris@0: $this Chris@0: ->setName('trace') Chris@0: ->setDefinition([ Chris@0: new InputOption('include-psy', 'p', InputOption::VALUE_NONE, 'Include Psy in the call stack.'), Chris@0: new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Only include NUM lines.'), Chris@0: Chris@0: $grep, Chris@0: $insensitive, Chris@0: $invert, Chris@0: ]) Chris@0: ->setDescription('Show the current call stack.') Chris@0: ->setHelp( Chris@0: <<<'HELP' Chris@0: Show the current call stack. Chris@0: Chris@0: Optionally, include PsySH in the call stack by passing the --include-psy option. Chris@0: Chris@0: e.g. Chris@0: > trace -n10 Chris@0: > trace --include-psy Chris@0: HELP Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function execute(InputInterface $input, OutputInterface $output) Chris@0: { Chris@0: $this->filter->bind($input); Chris@0: $trace = $this->getBacktrace(new \Exception(), $input->getOption('num'), $input->getOption('include-psy')); Chris@0: $output->page($trace, ShellOutput::NUMBER_LINES); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get a backtrace for an exception. Chris@0: * Chris@0: * Optionally limit the number of rows to include with $count, and exclude Chris@0: * Psy from the trace. Chris@0: * Chris@0: * @param \Exception $e The exception with a backtrace Chris@0: * @param int $count (default: PHP_INT_MAX) Chris@0: * @param bool $includePsy (default: true) Chris@0: * Chris@0: * @return array Formatted stacktrace lines Chris@0: */ Chris@0: protected function getBacktrace(\Exception $e, $count = null, $includePsy = true) Chris@0: { Chris@4: if ($cwd = \getcwd()) { Chris@4: $cwd = \rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; Chris@0: } Chris@0: Chris@0: if ($count === null) { Chris@0: $count = PHP_INT_MAX; Chris@0: } Chris@0: Chris@0: $lines = []; Chris@0: Chris@0: $trace = $e->getTrace(); Chris@4: \array_unshift($trace, [ Chris@0: 'function' => '', Chris@0: 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', Chris@0: 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', Chris@0: 'args' => [], Chris@0: ]); Chris@0: Chris@0: if (!$includePsy) { Chris@4: for ($i = \count($trace) - 1; $i >= 0; $i--) { Chris@0: $thing = isset($trace[$i]['class']) ? $trace[$i]['class'] : $trace[$i]['function']; Chris@4: if (\preg_match('/\\\\?Psy\\\\/', $thing)) { Chris@4: $trace = \array_slice($trace, $i + 1); Chris@0: break; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@4: for ($i = 0, $count = \min($count, \count($trace)); $i < $count; $i++) { Chris@0: $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; Chris@0: $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; Chris@0: $function = $trace[$i]['function']; Chris@0: $file = isset($trace[$i]['file']) ? $this->replaceCwd($cwd, $trace[$i]['file']) : 'n/a'; Chris@0: $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; Chris@0: Chris@0: // Leave execution loop out of the `eval()'d code` lines Chris@4: if (\preg_match("#/src/Execution(?:Loop)?Closure.php\(\d+\) : eval\(\)'d code$#", \str_replace('\\', '/', $file))) { Chris@0: $file = "eval()'d code"; Chris@0: } Chris@0: Chris@0: // Skip any lines that don't match our filter options Chris@4: if (!$this->filter->match(\sprintf('%s%s%s() at %s:%s', $class, $type, $function, $file, $line))) { Chris@0: continue; Chris@0: } Chris@0: Chris@4: $lines[] = \sprintf( Chris@0: ' %s%s%s() at %s:%s', Chris@0: OutputFormatter::escape($class), Chris@0: OutputFormatter::escape($type), Chris@0: OutputFormatter::escape($function), Chris@0: OutputFormatter::escape($file), Chris@0: OutputFormatter::escape($line) Chris@0: ); Chris@0: } Chris@0: Chris@0: return $lines; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Replace the given directory from the start of a filepath. Chris@0: * Chris@0: * @param string $cwd Chris@0: * @param string $file Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: private function replaceCwd($cwd, $file) Chris@0: { Chris@0: if ($cwd === false) { Chris@0: return $file; Chris@0: } else { Chris@4: return \preg_replace('/^' . \preg_quote($cwd, '/') . '/', '', $file); Chris@0: } Chris@0: } Chris@0: }