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