From 7cb5e02a612573140fd2e47a965286680c70efb1 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Fri, 14 Jul 2023 17:59:03 -0500 Subject: [PATCH] Support editor link on QueryCollector --- src/DataCollector/QueryCollector.php | 51 ++++++----------- src/DataCollector/RouteCollector.php | 82 ++++------------------------ src/DataCollector/ViewCollector.php | 78 ++------------------------ src/LaravelDebugbar.php | 19 +++++++ src/Resources/laravel-debugbar.css | 8 +++ src/Resources/sqlqueries/widget.js | 12 +++- 6 files changed, 72 insertions(+), 178 deletions(-) diff --git a/src/DataCollector/QueryCollector.php b/src/DataCollector/QueryCollector.php index 2ca8cfcc2..bc5c6441e 100644 --- a/src/DataCollector/QueryCollector.php +++ b/src/DataCollector/QueryCollector.php @@ -288,6 +288,7 @@ protected function parseTrace($index, array $trace) 'index' => $index, 'namespace' => null, 'name' => null, + 'file' => null, 'line' => isset($trace['line']) ? $trace['line'] : '?', ]; @@ -302,33 +303,36 @@ protected function parseTrace($index, array $trace) isset($trace['file']) && !$this->fileIsInExcludedPath($trace['file']) ) { - $file = $trace['file']; + $frame->file = $trace['file']; if (isset($trace['object']) && is_a($trace['object'], 'Twig_Template')) { - list($file, $frame->line) = $this->getTwigInfo($trace); - } elseif (strpos($file, storage_path()) !== false) { - $hash = pathinfo($file, PATHINFO_FILENAME); + list($frame->file, $frame->line) = $this->getTwigInfo($trace); + } elseif (strpos($frame->file, storage_path()) !== false) { + $hash = pathinfo($frame->file, PATHINFO_FILENAME); - if (! $frame->name = $this->findViewFromHash($hash)) { + if ($frame->name = $this->findViewFromHash($hash)) { + $frame->file = $frame->name[1]; + $frame->name = $frame->name[0]; + } else { $frame->name = $hash; } $frame->namespace = 'view'; return $frame; - } elseif (strpos($file, 'Middleware') !== false) { - $frame->name = $this->findMiddlewareFromFile($file); + } elseif (strpos($frame->file, 'Middleware') !== false) { + $frame->name = $this->findMiddlewareFromFile($frame->file); if ($frame->name) { $frame->namespace = 'middleware'; } else { - $frame->name = $this->normalizeFilename($file); + $frame->name = $this->normalizeFilePath($frame->file); } return $frame; } - $frame->name = $this->normalizeFilename($file); + $frame->name = $this->normalizeFilePath($frame->file); return $frame; } @@ -377,7 +381,7 @@ protected function findMiddlewareFromFile($file) * Find the template name from the hash. * * @param string $hash - * @return null|string + * @return null|array */ protected function findViewFromHash($hash) { @@ -396,7 +400,7 @@ protected function findViewFromHash($hash) foreach ($property->getValue($finder) as $name => $path) { if (($xxh128Exists && hash('xxh128', 'v2' . $path) == $hash) || sha1('v2' . $path) == $hash) { - return $name; + return [$name, $path]; } } } @@ -422,27 +426,6 @@ protected function getTwigInfo($trace) return [$file, -1]; } - /** - * Shorten the path by removing the relative links and base dir - * - * @param string $path - * @return string - */ - protected function normalizeFilename($path) - { - if (file_exists($path)) { - $path = realpath($path); - } - - $basepath = base_path(); - - if (! str_starts_with($path, $basepath)) { - return $path; - } - - return substr($path, strlen($basepath)); - } - /** * Collect a database transaction event. * @param string $event @@ -492,6 +475,7 @@ public function collect() $statements = []; foreach ($queries as $query) { + $source = reset($query['source']); $totalTime += $query['time']; $statements[] = [ @@ -504,7 +488,8 @@ public function collect() 'backtrace' => array_values($query['source']), 'duration' => $query['time'], 'duration_str' => ($query['type'] == 'transaction') ? '' : $this->formatDuration($query['time']), - 'stmt_id' => $this->getDataFormatter()->formatSource(reset($query['source'])), + 'stmt_id' => $this->getDataFormatter()->formatSource($source), + 'xdebug_link' => is_object($source) ? $this->getXdebugLink($source->file ?: '', $source->line) : null, 'connection' => $query['connection'], ]; diff --git a/src/DataCollector/RouteCollector.php b/src/DataCollector/RouteCollector.php index a255302f3..11b0b5deb 100644 --- a/src/DataCollector/RouteCollector.php +++ b/src/DataCollector/RouteCollector.php @@ -9,7 +9,6 @@ use Illuminate\Routing\Route; use Illuminate\Routing\Router; use Illuminate\Support\Facades\Config; -use InvalidArgumentException; /** * Based on Illuminate\Foundation\Console\RoutesCommand for Taylor Otwell @@ -25,30 +24,6 @@ class RouteCollector extends DataCollector implements Renderable */ protected $router; - /** - * A list of known editor strings. - * - * @var array - */ - protected $editors = [ - 'sublime' => 'subl://open?url=file://%file&line=%line', - 'textmate' => 'txmt://open?url=file://%file&line=%line', - 'emacs' => 'emacs://open?url=file://%file&line=%line', - 'macvim' => 'mvim://open/?url=file://%file&line=%line', - 'phpstorm' => 'phpstorm://open?file=%file&line=%line', - 'idea' => 'idea://open?file=%file&line=%line', - 'vscode' => 'vscode://file/%file:%line', - 'vscode-insiders' => 'vscode-insiders://file/%file:%line', - 'vscode-remote' => 'vscode://vscode-remote/%file:%line', - 'vscode-insiders-remote' => 'vscode-insiders://vscode-remote/%file:%line', - 'vscodium' => 'vscodium://file/%file:%line', - 'nova' => 'nova://core/open/file?filename=%file&line=%line', - 'xdebug' => 'xdebug://%file@%line', - 'atom' => 'atom://core/open/file?filename=%file&line=%line', - 'espresso' => 'x-espresso://open?filepath=%file&lines=%line', - 'netbeans' => 'netbeans://open/?f=%file:%line', - ]; - public function __construct(Router $router) { $this->router = $router; @@ -100,10 +75,17 @@ protected function getRouteInformation($route) } if (isset($reflector)) { - $filename = ltrim(str_replace(base_path(), '', $reflector->getFileName()), '/'); - - if ($href = $this->getEditorHref($reflector->getFileName(), $reflector->getStartLine())) { - $result['file'] = sprintf('%s:%s-%s', $href, $filename, $reflector->getStartLine(), $reflector->getEndLine()); + $filename = $this->normalizeFilePath($reflector->getFileName()); + + if ($link = $this->getXdebugLink($reflector->getFileName(), $reflector->getStartLine())) { + $result['file'] = sprintf( + '%s:%s-%s', + $link['url'], + $link['ajax'] ? 'event.preventDefault();$.ajax(this.href);' : '', + $filename, + $reflector->getStartLine(), + $reflector->getEndLine() + ); } else { $result['file'] = sprintf('%s:%s-%s', $filename, $reflector->getStartLine(), $reflector->getEndLine()); } @@ -175,46 +157,4 @@ protected function displayRoutes(array $routes) $this->table->render($this->getOutput()); } - - /** - * Get the editor href for a given file and line, if available. - * - * @param string $filePath - * @param int $line - * - * @throws InvalidArgumentException If editor resolver does not return a string - * - * @return null|string - */ - protected function getEditorHref($filePath, $line) - { - if (empty(config('debugbar.editor'))) { - return null; - } - - if (empty($this->editors[config('debugbar.editor')])) { - throw new InvalidArgumentException( - 'Unknown editor identifier: ' . config('debugbar.editor') . '. Known editors:' . - implode(', ', array_keys($this->editors)) - ); - } - - $filePath = $this->replaceSitesPath($filePath); - - $url = str_replace(['%file', '%line'], [$filePath, $line], $this->editors[config('debugbar.editor')]); - - return $url; - } - - /** - * Replace remote path - * - * @param string $filePath - * - * @return string - */ - protected function replaceSitesPath($filePath) - { - return str_replace(config('debugbar.remote_sites_path'), config('debugbar.local_sites_path'), $filePath); - } } diff --git a/src/DataCollector/ViewCollector.php b/src/DataCollector/ViewCollector.php index 1cefe5c7a..51e5120e1 100644 --- a/src/DataCollector/ViewCollector.php +++ b/src/DataCollector/ViewCollector.php @@ -5,7 +5,6 @@ use Barryvdh\Debugbar\DataFormatter\SimpleFormatter; use DebugBar\Bridge\Twig\TwigCollector; use Illuminate\View\View; -use InvalidArgumentException; class ViewCollector extends TwigCollector { @@ -14,34 +13,10 @@ class ViewCollector extends TwigCollector protected $collect_data; protected $exclude_paths; - /** - * A list of known editor strings. - * - * @var array - */ - protected $editors = [ - 'sublime' => 'subl://open?url=file://%file&line=%line', - 'textmate' => 'txmt://open?url=file://%file&line=%line', - 'emacs' => 'emacs://open?url=file://%file&line=%line', - 'macvim' => 'mvim://open/?url=file://%file&line=%line', - 'phpstorm' => 'phpstorm://open?file=%file&line=%line', - 'idea' => 'idea://open?file=%file&line=%line', - 'vscode' => 'vscode://file/%file:%line', - 'vscode-insiders' => 'vscode-insiders://file/%file:%line', - 'vscode-remote' => 'vscode://vscode-remote/%file:%line', - 'vscode-insiders-remote' => 'vscode-insiders://vscode-remote/%file:%line', - 'vscodium' => 'vscodium://file/%file:%line', - 'nova' => 'nova://core/open/file?filename=%file&line=%line', - 'xdebug' => 'xdebug://%file@%line', - 'atom' => 'atom://core/open/file?filename=%file&line=%line', - 'espresso' => 'x-espresso://open?filepath=%file&lines=%line', - 'netbeans' => 'netbeans://open/?f=%file:%line', - ]; - /** * Create a ViewCollector * - * @param bool $collectData Collects view data when tru + * @param bool $collectData Collects view data when true * @param string[] $excludePaths Paths to exclude from collection */ public function __construct($collectData = true, $excludePaths = []) @@ -73,36 +48,6 @@ public function getWidgets() ]; } - /** - * Get the editor href for a given file and line, if available. - * - * @param string $filePath - * @param int $line - * - * @throws InvalidArgumentException If editor resolver does not return a string - * - * @return null|string - */ - protected function getEditorHref($filePath, $line) - { - if (empty(config('debugbar.editor'))) { - return null; - } - - if (empty($this->editors[config('debugbar.editor')])) { - throw new InvalidArgumentException( - 'Unknown editor identifier: ' . config('debugbar.editor') . '. Known editors:' . - implode(', ', array_keys($this->editors)) - ); - } - - $filePath = $this->replaceSitesPath($filePath); - - $url = str_replace(['%file', '%line'], [$filePath, $line], $this->editors[config('debugbar.editor')]); - - return $url; - } - /** * Add a View instance to the Collector * @@ -115,7 +60,7 @@ public function addView(View $view) $type = ''; if ($path && is_string($path)) { - $path = ltrim(str_replace(base_path(), '', realpath($path)), '/'); + $path = $this->normalizeFilePath($path); if (substr($path, -10) == '.blade.php') { $type = 'blade'; @@ -128,7 +73,7 @@ public function addView(View $view) } foreach ($this->exclude_paths as $excludePath) { - if (strpos($path, $excludePath) !== false) { + if (str_starts_with($path, $excludePath)) { return; } } @@ -147,11 +92,10 @@ public function addView(View $view) 'name' => $path ? sprintf('%s (%s)', $name, $path) : $name, 'param_count' => count($params), 'params' => $params, - 'type' => $type, - 'editorLink' => $this->getEditorHref($view->getPath(), 0), + 'type' => $type, ]; - if ($this->getXdebugLink($path)) { + if ($this->getXdebugLinkTemplate()) { $template['xdebug_link'] = $this->getXdebugLink(realpath($view->getPath())); } @@ -167,16 +111,4 @@ public function collect() 'templates' => $templates, ]; } - - /** - * Replace remote path - * - * @param string $filePath - * - * @return string - */ - protected function replaceSitesPath($filePath) - { - return str_replace(config('debugbar.remote_sites_path'), config('debugbar.local_sites_path'), $filePath); - } } diff --git a/src/LaravelDebugbar.php b/src/LaravelDebugbar.php index 2213f2699..4c0e208bb 100644 --- a/src/LaravelDebugbar.php +++ b/src/LaravelDebugbar.php @@ -96,6 +96,8 @@ class LaravelDebugbar extends DebugBar */ protected $is_lumen = false; + protected array $editorTemplateArgs = []; + /** * @param Application $app */ @@ -139,6 +141,8 @@ public function boot() /** @var Application $app */ $app = $this->app; + $this->editorTemplateArgs = [$this->app['config']->get('debugbar.editor'), $this->getRemoteServerReplacements()]; + // Set custom error handler if ($app['config']->get('debugbar.error_handler', false)) { set_error_handler([$this, 'handleError']); @@ -585,6 +589,10 @@ public function addCollector(DataCollectorInterface $collector) if (method_exists($collector, 'useHtmlVarDumper')) { $collector->useHtmlVarDumper(); } + if (method_exists($collector, 'setEditorLinkTemplate')) { + $collector->setEditorLinkTemplate($this->editorTemplateArgs[0]); + $collector->addXdebugReplacements($this->editorTemplateArgs[1]); + } return $this; } @@ -1167,6 +1175,17 @@ protected function addServerTimingHeaders(Response $response) } } + /** + * @return array + */ + private function getRemoteServerReplacements() + { + $localPath = $this->app['config']->get('debugbar.local_sites_path', ''); + $remotePaths = array_filter(explode(',', $this->app['config']->get('debugbar.remote_sites_path', ''))) ?: [base_path()]; + + return array_fill_keys($remotePaths, $localPath); + } + /** * @return \Monolog\Logger * @throws Exception diff --git a/src/Resources/laravel-debugbar.css b/src/Resources/laravel-debugbar.css index c2163c7ea..9825a45cf 100644 --- a/src/Resources/laravel-debugbar.css +++ b/src/Resources/laravel-debugbar.css @@ -578,6 +578,14 @@ ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widget margin-right: 5px; } +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id a { + color: #888; +} + +ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id a:hover { + color: #aaa; +} + ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item table.phpdebugbar-widgets-params { background-color: #fdfdfd; margin: 10px 0px; diff --git a/src/Resources/sqlqueries/widget.js b/src/Resources/sqlqueries/widget.js index ebed4f08d..27a833542 100644 --- a/src/Resources/sqlqueries/widget.js +++ b/src/Resources/sqlqueries/widget.js @@ -83,7 +83,17 @@ $('').addClass(csscls('row-count')).text(stmt.row_count).appendTo(li); } if (typeof(stmt.stmt_id) != 'undefined' && stmt.stmt_id) { - $('').addClass(csscls('stmt-id')).text(stmt.stmt_id).appendTo(li); + $('').addClass(csscls('stmt-id')) + .append(! stmt.xdebug_link ? $('').text(stmt.stmt_id).html() : $('') + .attr('href', stmt.xdebug_link.url).text(stmt.stmt_id) + .on('click', function (event) { + event.stopPropagation(); + if (stmt.xdebug_link.ajax) { + event.preventDefault(); + $.ajax(stmt.xdebug_link.url); + } + }) + ).appendTo(li); } if (stmt.connection) { $('').addClass(csscls('database')).text(stmt.connection).appendTo(li);