diff --git a/config/debugbar.php b/config/debugbar.php index c6f8d772..31f2b0c3 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -215,10 +215,13 @@ 'show_name' => true, // Also show the users name/email in the debugbar 'show_guards' => true, // Show the guards that are used ], + 'gate' => [ + 'trace' => false, // Trace the origin of the Gate checks + ], 'db' => [ 'with_params' => true, // Render SQL with the parameters substituted 'exclude_paths' => [ // Paths to exclude entirely from the collector -// 'vendor/laravel/framework/src/Illuminate/Session', // Exclude sessions queries + //'vendor/laravel/framework/src/Illuminate/Session', // Exclude sessions queries ], 'backtrace' => true, // Use a backtrace to find the origin of the query in your files. 'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults) diff --git a/src/DataCollector/GateCollector.php b/src/DataCollector/GateCollector.php index b262718e..ebb5a781 100644 --- a/src/DataCollector/GateCollector.php +++ b/src/DataCollector/GateCollector.php @@ -8,20 +8,31 @@ use Illuminate\Contracts\Auth\Access\Gate; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Routing\Router; use Symfony\Component\VarDumper\Cloner\VarCloner; use Illuminate\Support\Str; /** - * Collector for Laravel's Auth provider + * Collector for Laravel's gate checks */ class GateCollector extends MessagesCollector { + /** @var int */ + protected $backtraceLimit = 15; + + /** @var array */ + protected $reflection = []; + + /** @var \Illuminate\Routing\Router */ + protected $router; + /** * @param Gate $gate */ - public function __construct(Gate $gate) + public function __construct(Gate $gate, Router $router) { parent::__construct('gate'); + $this->router = $router; $this->setDataFormatter(new SimpleFormatter()); $gate->after(function ($user, $ability, $result, $arguments = []) { $this->addCheck($user, $ability, $result, $arguments); @@ -82,4 +93,90 @@ public function addCheck($user, $ability, $result, $arguments = []) 'arguments' => $this->getDataFormatter()->formatVar($arguments), ], $label, false); } + + /** + * @param array $stacktrace + * + * @return array + */ + protected function getStackTraceItem($stacktrace) + { + foreach ($stacktrace as $i => $trace) { + if (!isset($trace['file'])) { + continue; + } + + if (str_ends_with($trace['file'], 'Illuminate/Routing/ControllerDispatcher.php')) { + $trace = $this->findControllerFromDispatcher($trace); + } elseif (str_starts_with($trace['file'], storage_path())) { + $hash = pathinfo($trace['file'], PATHINFO_FILENAME); + + if ($file = $this->findViewFromHash($hash)) { + $trace['file'] = $file; + } + } + + if ($this->fileIsInExcludedPath($trace['file'])) { + continue; + } + + return $trace; + } + + return $stacktrace[0]; + } + + /** + * Find the route action file + * + * @param array $trace + * @return array + */ + protected function findControllerFromDispatcher($trace) + { + /** @var \Closure|string|array $action */ + $action = $this->router->current()->getAction('uses'); + + if (is_string($action)) { + [$controller, $method] = explode('@', $action); + + $reflection = new \ReflectionMethod($controller, $method); + $trace['file'] = $reflection->getFileName(); + $trace['line'] = $reflection->getStartLine(); + } elseif ($action instanceof \Closure) { + $reflection = new \ReflectionFunction($action); + $trace['file'] = $reflection->getFileName(); + $trace['line'] = $reflection->getStartLine(); + } + + return $trace; + } + + /** + * Find the template name from the hash. + * + * @param string $hash + * @return null|array + */ + protected function findViewFromHash($hash) + { + $finder = app('view')->getFinder(); + + if (isset($this->reflection['viewfinderViews'])) { + $property = $this->reflection['viewfinderViews']; + } else { + $reflection = new \ReflectionClass($finder); + $property = $reflection->getProperty('views'); + $property->setAccessible(true); + $this->reflection['viewfinderViews'] = $property; + } + + $xxh128Exists = in_array('xxh128', hash_algos()); + + foreach ($property->getValue($finder) as $name => $path) { + if (($xxh128Exists && hash('xxh128', 'v2' . $path) == $hash) || sha1('v2' . $path) == $hash) { + return $path; + } + } + } } diff --git a/src/LaravelDebugbar.php b/src/LaravelDebugbar.php index 5ca7e317..72a443e8 100644 --- a/src/LaravelDebugbar.php +++ b/src/LaravelDebugbar.php @@ -566,6 +566,11 @@ public function __toString(): string if ($this->shouldCollect('gate', false)) { try { $this->addCollector($app->make(GateCollector::class)); + + if ($config->get('debugbar.options.gate.trace', false)) { + $this['gate']->collectFileTrace(true); + $this['gate']->addBacktraceExcludePaths($config->get('debugbar.options.gate.exclude_paths',[])); + } } catch (Exception $e) { $this->addCollectorException('Cannot add GateCollector', $e); }