├── .gitignore ├── src ├── Facade.php ├── Resources │ ├── vendor │ │ └── font-awesome │ │ │ └── generator_config.txt │ ├── sqlqueries │ │ └── widget.js │ └── laravel-debugbar.css ├── Controllers │ ├── BaseController.php │ ├── AssetController.php │ └── OpenHandlerController.php ├── Console │ ├── PublishCommand.php │ └── ClearCommand.php ├── DataCollector │ ├── GateCollector.php │ ├── SessionCollector.php │ ├── LaravelCollector.php │ ├── ViewCollector.php │ ├── MultiAuthCollector.php │ ├── AuthCollector.php │ ├── EventCollector.php │ ├── IlluminateRouteCollector.php │ ├── LogsCollector.php │ ├── FilesCollector.php │ ├── SymfonyRequestCollector.php │ └── QueryCollector.php ├── LumenServiceProvider.php ├── migrations │ └── 2014_12_01_120000_create_phpdebugbar_storage_table.php ├── Twig │ ├── Node │ │ └── StopwatchNode.php │ ├── Extension │ │ ├── Stopwatch.php │ │ ├── Dump.php │ │ └── Debug.php │ └── TokenParser │ │ └── StopwatchTokenParser.php ├── DataFormatter │ └── QueryFormatter.php ├── helpers.php ├── SymfonyHttpDriver.php ├── Middleware │ └── Debugbar.php ├── Support │ └── Clockwork │ │ ├── ClockworkCollector.php │ │ └── Converter.php ├── JavascriptRenderer.php ├── Storage │ └── FilesystemStorage.php ├── ServiceProvider.php └── LaravelDebugbar.php ├── LICENSE ├── composer.json ├── changelog.md ├── readme.md └── config └── debugbar.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /src/Facade.php: -------------------------------------------------------------------------------- 1 | debugbar = $debugbar; 16 | 17 | if ($request->hasSession()){ 18 | $request->session()->reflash(); 19 | } 20 | } 21 | } 22 | 23 | } else { 24 | 25 | class BaseController 26 | { 27 | protected $debugbar; 28 | 29 | public function __construct(Request $request, LaravelDebugbar $debugbar) 30 | { 31 | $this->debugbar = $debugbar; 32 | 33 | if ($request->hasSession()){ 34 | $request->session()->reflash(); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Console/PublishCommand.php: -------------------------------------------------------------------------------- 1 | 10 | * @deprecated No longer needed because of the AssetController 11 | */ 12 | class PublishCommand extends Command 13 | { 14 | /** 15 | * The console command name. 16 | * 17 | * @var string 18 | */ 19 | protected $name = 'debugbar:publish'; 20 | 21 | /** 22 | * The console command description. 23 | * 24 | * @var string 25 | */ 26 | protected $description = 'Publish the Debugbar assets'; 27 | 28 | /** 29 | * Execute the console command. 30 | * 31 | * @return void 32 | */ 33 | public function fire() 34 | { 35 | $this->info( 36 | 'NOTICE: Since laravel-debugbar 1.7.x, publishing assets is no longer necessary. The assets in public/packages/barryvdh/laravel-debugbar and maximebf/php-debugbar can be safely removed.' 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013-2014 Barry vd. Heuvel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "barryvdh/laravel-debugbar", 3 | "description": "PHP Debugbar integration for Laravel", 4 | "keywords": ["laravel", "debugbar", "profiler", "debug", "webprofiler"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Barry vd. Heuvel", 9 | "email": "barryvdh@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.5.9", 14 | "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", 15 | "symfony/finder": "~2.7|~3.0", 16 | "maximebf/debugbar": "~1.13.0" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Barryvdh\\Debugbar\\": "src/" 21 | }, 22 | "files": [ 23 | "src/helpers.php" 24 | ] 25 | }, 26 | "extra": { 27 | "branch-alias": { 28 | "dev-master": "2.4-dev" 29 | }, 30 | "laravel": { 31 | "providers": [ 32 | "Barryvdh\\Debugbar\\ServiceProvider" 33 | ], 34 | "aliases": { 35 | "Debugbar": "Barryvdh\\Debugbar\\Facade" 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Console/ClearCommand.php: -------------------------------------------------------------------------------- 1 | debugbar = $debugbar; 15 | 16 | parent::__construct(); 17 | } 18 | 19 | public function fire() 20 | { 21 | $this->debugbar->boot(); 22 | 23 | if ($storage = $this->debugbar->getStorage()) { 24 | try 25 | { 26 | $storage->clear(); 27 | } catch(\InvalidArgumentException $e) { 28 | // hide InvalidArgumentException if storage location does not exist 29 | if(strpos($e->getMessage(), 'does not exist') === false) { 30 | throw $e; 31 | } 32 | } 33 | $this->info('Debugbar Storage cleared!'); 34 | } else { 35 | $this->error('No Debugbar Storage found..'); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/DataCollector/GateCollector.php: -------------------------------------------------------------------------------- 1 | exporter = new ValueExporter(); 24 | 25 | if (method_exists($gate, 'after')) { 26 | $gate->after([$this, 'addCheck']); 27 | } 28 | } 29 | 30 | public function addCheck(Authenticatable $user, $ability, $result, $arguments = []) 31 | { 32 | $label = $result ? 'success' : 'error'; 33 | 34 | $this->addMessage([ 35 | 'ability' => $ability, 36 | 'result' => $result, 37 | 'user' => $user->getAuthIdentifier(), 38 | 'arguments' => $this->exporter->exportValue($arguments), 39 | ], $label, false); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/LumenServiceProvider.php: -------------------------------------------------------------------------------- 1 | app; 18 | } 19 | 20 | /** 21 | * Get the config path 22 | * 23 | * @return string 24 | */ 25 | protected function getConfigPath() 26 | { 27 | return base_path('config/debugbar.php'); 28 | } 29 | 30 | /** 31 | * Register the Debugbar Middleware 32 | * 33 | * @param string $middleware 34 | */ 35 | protected function registerMiddleware($middleware) 36 | { 37 | $this->app->middleware([$middleware]); 38 | } 39 | 40 | /** 41 | * Check the App Debug status 42 | */ 43 | protected function checkAppDebug() 44 | { 45 | return env('APP_DEBUG'); 46 | } 47 | 48 | /** 49 | * Get the services provided by the provider. 50 | * 51 | * @return array 52 | */ 53 | public function provides() 54 | { 55 | return ['debugbar', 'command.debugbar.clear']; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/migrations/2014_12_01_120000_create_phpdebugbar_storage_table.php: -------------------------------------------------------------------------------- 1 | string('id'); 17 | $table->longText('data'); 18 | $table->string('meta_utime'); 19 | $table->dateTime('meta_datetime'); 20 | $table->string('meta_uri'); 21 | $table->string('meta_ip'); 22 | $table->string('meta_method'); 23 | 24 | $table->primary('id'); 25 | $table->index('meta_utime'); 26 | $table->index('meta_datetime'); 27 | $table->index('meta_uri'); 28 | $table->index('meta_ip'); 29 | $table->index('meta_method'); 30 | }); 31 | } 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::drop('phpdebugbar'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Twig/Node/StopwatchNode.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class StopwatchNode extends \Twig_Node 9 | { 10 | public function __construct( 11 | \Twig_NodeInterface $name, 12 | $body, 13 | \Twig_Node_Expression_AssignName $var, 14 | $lineno = 0, 15 | $tag = null 16 | ) { 17 | parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); 18 | } 19 | 20 | public function compile(\Twig_Compiler $compiler) 21 | { 22 | $compiler 23 | ->addDebugInfo($this) 24 | ->write('') 25 | ->subcompile($this->getNode('var')) 26 | ->raw(' = ') 27 | ->subcompile($this->getNode('name')) 28 | ->write(";\n") 29 | ->write("\$this->env->getExtension('stopwatch')->getDebugbar()->startMeasure(") 30 | ->subcompile($this->getNode('var')) 31 | ->raw(");\n") 32 | ->subcompile($this->getNode('body')) 33 | ->write("\$this->env->getExtension('stopwatch')->getDebugbar()->stopMeasure(") 34 | ->subcompile($this->getNode('var')) 35 | ->raw(");\n"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Twig/Extension/Stopwatch.php: -------------------------------------------------------------------------------- 1 | bound('debugbar')) { 26 | $this->debugbar = $app['debugbar']; 27 | } else { 28 | $this->debugbar = null; 29 | } 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | public function getName() 36 | { 37 | return 'stopwatch'; 38 | } 39 | 40 | public function getTokenParsers() 41 | { 42 | return [ 43 | /* 44 | * {% stopwatch foo %} 45 | * Some stuff which will be recorded on the timeline 46 | * {% endstopwatch %} 47 | */ 48 | new StopwatchTokenParser($this->debugbar !== null), 49 | ]; 50 | } 51 | 52 | public function getDebugbar() 53 | { 54 | return $this->debugbar; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Controllers/AssetController.php: -------------------------------------------------------------------------------- 1 | debugbar->getJavascriptRenderer(); 15 | 16 | $content = $renderer->dumpAssetsToString('js'); 17 | 18 | $response = new Response( 19 | $content, 200, [ 20 | 'Content-Type' => 'text/javascript', 21 | ] 22 | ); 23 | 24 | return $this->cacheResponse($response); 25 | } 26 | 27 | /** 28 | * Return the stylesheets for the Debugbar 29 | * 30 | * @return \Symfony\Component\HttpFoundation\Response 31 | */ 32 | public function css() 33 | { 34 | $renderer = $this->debugbar->getJavascriptRenderer(); 35 | 36 | $content = $renderer->dumpAssetsToString('css'); 37 | 38 | $response = new Response( 39 | $content, 200, [ 40 | 'Content-Type' => 'text/css', 41 | ] 42 | ); 43 | 44 | return $this->cacheResponse($response); 45 | } 46 | 47 | /** 48 | * Cache the response 1 year (31536000 sec) 49 | */ 50 | protected function cacheResponse(Response $response) 51 | { 52 | $response->setSharedMaxAge(31536000); 53 | $response->setMaxAge(31536000); 54 | $response->setExpires(new \DateTime('+1 year')); 55 | 56 | return $response; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Controllers/OpenHandlerController.php: -------------------------------------------------------------------------------- 1 | debugbar; 13 | 14 | if (!$debugbar->isEnabled()) { 15 | $this->app->abort('500', 'Debugbar is not enabled'); 16 | } 17 | 18 | $openHandler = new OpenHandler($debugbar); 19 | 20 | $data = $openHandler->handle(null, false, false); 21 | 22 | return new Response( 23 | $data, 200, [ 24 | 'Content-Type' => 'application/json' 25 | ] 26 | ); 27 | } 28 | 29 | /** 30 | * Return Clockwork output 31 | * 32 | * @param $id 33 | * @return mixed 34 | * @throws \DebugBar\DebugBarException 35 | */ 36 | public function clockwork($id) 37 | { 38 | $request = [ 39 | 'op' => 'get', 40 | 'id' => $id, 41 | ]; 42 | 43 | $debugbar = $this->debugbar; 44 | 45 | if (!$debugbar->isEnabled()) { 46 | $this->app->abort('500', 'Debugbar is not enabled'); 47 | } 48 | 49 | $openHandler = new OpenHandler($debugbar); 50 | 51 | $data = $openHandler->handle($request, false, false); 52 | 53 | // Convert to Clockwork 54 | $converter = new Converter(); 55 | $output = $converter->convert(json_decode($data, true)); 56 | 57 | return response()->json($output); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/DataCollector/SessionCollector.php: -------------------------------------------------------------------------------- 1 | session = $session; 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function collect() 28 | { 29 | $data = []; 30 | foreach ($this->session->all() as $key => $value) { 31 | $data[$key] = is_string($value) ? $value : $this->formatVar($value); 32 | } 33 | return $data; 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | public function getName() 40 | { 41 | return 'session'; 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | public function getWidgets() 48 | { 49 | return [ 50 | "session" => [ 51 | "icon" => "archive", 52 | "widget" => "PhpDebugBar.Widgets.VariableListWidget", 53 | "map" => "session", 54 | "default" => "{}" 55 | ] 56 | ]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Twig/TokenParser/StopwatchTokenParser.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class StopwatchTokenParser extends \Twig_TokenParser 11 | { 12 | protected $debugbarAvailable; 13 | 14 | public function __construct($debugbarAvailable) 15 | { 16 | $this->debugbarAvailable = $debugbarAvailable; 17 | } 18 | 19 | public function parse(\Twig_Token $token) 20 | { 21 | $lineno = $token->getLine(); 22 | $stream = $this->parser->getStream(); 23 | 24 | // {% stopwatch 'bar' %} 25 | $name = $this->parser->getExpressionParser()->parseExpression(); 26 | 27 | $stream->expect(\Twig_Token::BLOCK_END_TYPE); 28 | 29 | // {% endstopwatch %} 30 | $body = $this->parser->subparse([$this, 'decideStopwatchEnd'], true); 31 | $stream->expect(\Twig_Token::BLOCK_END_TYPE); 32 | 33 | if ($this->debugbarAvailable) { 34 | return new StopwatchNode( 35 | $name, 36 | $body, 37 | new \Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), 38 | $lineno, 39 | $this->getTag() 40 | ); 41 | } 42 | 43 | return $body; 44 | } 45 | 46 | public function getTag() 47 | { 48 | return 'stopwatch'; 49 | } 50 | 51 | public function decideStopwatchEnd(\Twig_Token $token) 52 | { 53 | return $token->test('endstopwatch'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DataCollector/LaravelCollector.php: -------------------------------------------------------------------------------- 1 | app = $app; 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | public function collect() 26 | { 27 | // Fallback if not injected 28 | $app = $this->app ?: app(); 29 | 30 | return [ 31 | "version" => $app::VERSION, 32 | "environment" => $app->environment(), 33 | "locale" => $app->getLocale(), 34 | ]; 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | public function getName() 41 | { 42 | return 'laravel'; 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | public function getWidgets() 49 | { 50 | return [ 51 | "version" => [ 52 | "icon" => "github", 53 | "tooltip" => "Version", 54 | "map" => "laravel.version", 55 | "default" => "" 56 | ], 57 | "environment" => [ 58 | "icon" => "desktop", 59 | "tooltip" => "Environment", 60 | "map" => "laravel.environment", 61 | "default" => "" 62 | ], 63 | "locale" => [ 64 | "icon" => "flag", 65 | "tooltip" => "Current locale", 66 | "map" => "laravel.locale", 67 | "default" => "", 68 | ], 69 | ]; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/DataFormatter/QueryFormatter.php: -------------------------------------------------------------------------------- 1 | namespace) { 68 | $parts['namespace'] = $source->namespace . '::'; 69 | } 70 | 71 | $parts['name'] = $source->name; 72 | $parts['line'] = ':' . $source->line; 73 | 74 | return implode($parts); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | addMessage($value, 'debug'); 27 | } 28 | } 29 | } 30 | 31 | if (!function_exists('start_measure')) { 32 | /** 33 | * Starts a measure 34 | * 35 | * @param string $name Internal name, used to stop the measure 36 | * @param string $label Public name 37 | */ 38 | function start_measure($name, $label = null) 39 | { 40 | app('debugbar')->startMeasure($name, $label); 41 | } 42 | } 43 | 44 | if (!function_exists('stop_measure')) { 45 | /** 46 | * Stop a measure 47 | * 48 | * @param string $name Internal name, used to stop the measure 49 | */ 50 | function stop_measure($name) 51 | { 52 | app('debugbar')->stopMeasure($name); 53 | } 54 | } 55 | 56 | if (!function_exists('add_measure')) { 57 | /** 58 | * Adds a measure 59 | * 60 | * @param string $label 61 | * @param float $start 62 | * @param float $end 63 | */ 64 | function add_measure($label, $start, $end) 65 | { 66 | app('debugbar')->addMeasure($label, $start, $end); 67 | } 68 | } 69 | 70 | if (!function_exists('measure')) { 71 | /** 72 | * Utility function to measure the execution of a Closure 73 | * 74 | * @param string $label 75 | * @param \Closure $closure 76 | */ 77 | function measure($label, \Closure $closure) 78 | { 79 | app('debugbar')->measure($label, $closure); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog for Laravel Debugbar 2 | 3 | ## 1.8.4 (2014-10-31) 4 | 5 | - Add Redis/PDO storage options 6 | 7 | ## 1.8.3 (2014-11-23) 8 | 9 | - Base EventCollector on TimeData Collector 10 | 11 | ## 1.8.2 (2014-11-18) 12 | 13 | - Use XHR handler instead of jQuery handler 14 | 15 | ## 1.8.1 (2014-11-14) 16 | 17 | - Fix compatability with Symfony 2.3 (Laravel 4.) 18 | 19 | ## 1.8.0 (2014-10-31) 20 | 21 | - Fix L5 compatability 22 | - add hints + explain options to QueryLogger 23 | - update to Debugbar 1.10.x 24 | - new ViewCollector layout with more information 25 | 26 | ## 1.7.7 (2014-09-15) 27 | 28 | - Make it compatible with Laravel 5.0-dev 29 | - Allow anonymous function as `enabled` setting (for IP checks etc) 30 | - Escape query bindings, to prevent executing of scripts/html 31 | 32 | ## 1.7.6 (2014-09-12) 33 | 34 | - Fix reflash bug 35 | - Fix caching of debugbar assets 36 | 37 | ## 1.7.5 (2014-09-12) 38 | 39 | - Reflash data for all debugbar requests 40 | 41 | ## 1.7.4 (2014-09-08) 42 | 43 | - Rename assets routes to prevent Nginx conflicts 44 | 45 | ## 1.7.3 (2014-09-05) 46 | 47 | - Add helper functions (debug(), add/start/stop_measure() and measure() 48 | - Collect data on responses that are not redirect/ajax/html also. 49 | 50 | ## 1.7.2 (2014-09-04) 51 | 52 | - Fix 4.0 compatibility (problem with Controller namespace) 53 | - Give deprecation notice instead of publishing assets. 54 | 55 | ## 1.7.1 (2014-09-03) 56 | 57 | - Deprecated `debugbar:publish` command in favor of AssetController 58 | - Fixed issue with detecting absolute paths in Windows 59 | 60 | ## 1.7.0 (2014-09-03) 61 | 62 | - Use AssetController instead of publishing assets to the public folder. 63 | - Inline fonts + images to base64 Data-URI 64 | - Use PSR-4 file structure 65 | 66 | ## 1.6.8 (2014-08-27) 67 | 68 | - Change OpenHandler layout 69 | - Add backtrace option for query origin 70 | 71 | ## 1.6.7 (2014-08-09) 72 | 73 | - Add Twig extensions for better integration with rcrowe/TwigBridge 74 | 75 | ## 1.6.6 (2014-07-08) 76 | 77 | - Check if Requests wantsJSON instead of only isXmlHttpRequest 78 | - Make sure closure for timing is run, even when disabled 79 | 80 | ## 1.6.5 (2014-06-24) 81 | 82 | - Add Laravel style 83 | 84 | ## 1.6.4 (2014-06-15) 85 | 86 | - Work on non-UTF-8 handling -------------------------------------------------------------------------------- /src/Twig/Extension/Dump.php: -------------------------------------------------------------------------------- 1 | formatter = $formatter; 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public function getName() 32 | { 33 | return 'Laravel_Debugbar_Dump'; 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | public function getFunctions() 40 | { 41 | return [ 42 | new Twig_SimpleFunction( 43 | 'dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true] 44 | ), 45 | ]; 46 | } 47 | 48 | /** 49 | * Based on Twig_Extension_Debug / twig_var_dump 50 | * (c) 2011 Fabien Potencier 51 | * 52 | * @param Twig_Environment $env 53 | * @param $context 54 | * 55 | * @return string 56 | */ 57 | public function dump(Twig_Environment $env, $context) 58 | { 59 | $output = ''; 60 | 61 | $count = func_num_args(); 62 | if (2 === $count) { 63 | $data = []; 64 | foreach ($context as $key => $value) { 65 | if (is_object($value)) { 66 | if (method_exists($value, 'toArray')) { 67 | $data[$key] = $value->toArray(); 68 | } else { 69 | $data[$key] = "Object (" . get_class($value) . ")"; 70 | } 71 | } else { 72 | $data[$key] = $value; 73 | } 74 | } 75 | $output .= $this->formatter->formatVar($data); 76 | } else { 77 | for ($i = 2; $i < $count; $i++) { 78 | $output .= $this->formatter->formatVar(func_get_arg($i)); 79 | } 80 | } 81 | 82 | return '
'.$output.'
'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/SymfonyHttpDriver.php: -------------------------------------------------------------------------------- 1 | session = $session; 22 | $this->response = $response; 23 | } 24 | 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | public function setHeaders(array $headers) 29 | { 30 | if (!is_null($this->response)) { 31 | $this->response->headers->add($headers); 32 | } 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public function isSessionStarted() 39 | { 40 | if (!$this->session->isStarted()) { 41 | $this->session->start(); 42 | } 43 | return $this->session->isStarted(); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public function setSessionValue($name, $value) 50 | { 51 | // In Laravel 5.4 the session changed to use their own custom implementation 52 | // instead of the one from Symfony. One of the changes was the set method 53 | // that was changed to put. Here we check if we are using the new one. 54 | if (method_exists($this->session, 'driver') && $this->session->driver() instanceof \Illuminate\Contracts\Session\Session) { 55 | $this->session->put($name, $value); 56 | return; 57 | } 58 | $this->session->set($name, $value); 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public function hasSessionValue($name) 65 | { 66 | return $this->session->has($name); 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | public function getSessionValue($name) 73 | { 74 | return $this->session->get($name); 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | public function deleteSessionValue($name) 81 | { 82 | $this->session->remove($name); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Twig/Extension/Debug.php: -------------------------------------------------------------------------------- 1 | bound('debugbar')) { 26 | $this->debugbar = $app['debugbar']; 27 | } else { 28 | $this->debugbar = null; 29 | } 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | public function getName() 36 | { 37 | return 'Laravel_Debugbar_Debug'; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | public function getFunctions() 44 | { 45 | return [ 46 | new Twig_SimpleFunction( 47 | 'debug', [$this, 'debug'], ['needs_context' => true, 'needs_environment' => true] 48 | ), 49 | ]; 50 | } 51 | 52 | /** 53 | * Based on Twig_Extension_Debug / twig_var_dump 54 | * (c) 2011 Fabien Potencier 55 | * 56 | * @param Twig_Environment $env 57 | * @param $context 58 | */ 59 | public function debug(Twig_Environment $env, $context) 60 | { 61 | if (!$env->isDebug() || !$this->debugbar) { 62 | return; 63 | } 64 | 65 | $count = func_num_args(); 66 | if (2 === $count) { 67 | $data = []; 68 | foreach ($context as $key => $value) { 69 | if (is_object($value)) { 70 | if (method_exists($value, 'toArray')) { 71 | $data[$key] = $value->toArray(); 72 | } else { 73 | $data[$key] = "Object (" . get_class($value) . ")"; 74 | } 75 | } else { 76 | $data[$key] = $value; 77 | } 78 | } 79 | $this->debugbar->addMessage($data); 80 | } else { 81 | for ($i = 2; $i < $count; $i++) { 82 | $this->debugbar->addMessage(func_get_arg($i)); 83 | } 84 | } 85 | 86 | return; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Middleware/Debugbar.php: -------------------------------------------------------------------------------- 1 | container = $container; 37 | $this->debugbar = $debugbar; 38 | } 39 | 40 | /** 41 | * Handle an incoming request. 42 | * 43 | * @param Request $request 44 | * @param Closure $next 45 | * @return mixed 46 | */ 47 | public function handle($request, Closure $next) 48 | { 49 | try { 50 | /** @var \Illuminate\Http\Response $response */ 51 | $response = $next($request); 52 | } catch (Exception $e) { 53 | $response = $this->handleException($request, $e); 54 | } catch (Error $error) { 55 | $e = new FatalThrowableError($error); 56 | $response = $this->handleException($request, $e); 57 | } 58 | 59 | // Modify the response to add the Debugbar 60 | $this->debugbar->modifyResponse($request, $response); 61 | 62 | return $response; 63 | 64 | } 65 | 66 | /** 67 | * Handle the given exception. 68 | * 69 | * (Copy from Illuminate\Routing\Pipeline by Taylor Otwell) 70 | * 71 | * @param $passable 72 | * @param Exception $e 73 | * @return mixed 74 | * @throws Exception 75 | */ 76 | protected function handleException($passable, Exception $e) 77 | { 78 | if (! $this->container->bound(ExceptionHandler::class) || ! $passable instanceof Request) { 79 | throw $e; 80 | } 81 | 82 | $handler = $this->container->make(ExceptionHandler::class); 83 | 84 | $handler->report($e); 85 | 86 | return $handler->render($passable, $e); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Support/Clockwork/ClockworkCollector.php: -------------------------------------------------------------------------------- 1 | 13 | * 14 | */ 15 | class ClockworkCollector extends DataCollector implements DataCollectorInterface, Renderable 16 | { 17 | /** @var \Symfony\Component\HttpFoundation\Request $request */ 18 | protected $request; 19 | /** @var \Symfony\Component\HttpFoundation\Request $response */ 20 | protected $response; 21 | /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */ 22 | protected $session; 23 | 24 | /** 25 | * Create a new SymfonyRequestCollector 26 | * 27 | * @param \Symfony\Component\HttpFoundation\Request $request 28 | * @param \Symfony\Component\HttpFoundation\Request $response 29 | * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session 30 | */ 31 | public function __construct($request, $response, $session = null) 32 | { 33 | $this->request = $request; 34 | $this->response = $response; 35 | $this->session = $session; 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | public function getName() 42 | { 43 | return 'clockwork'; 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public function getWidgets() 50 | { 51 | return null; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function collect() 58 | { 59 | $request = $this->request; 60 | $response = $this->response; 61 | 62 | $data = [ 63 | 'getData' => $request->query->all(), 64 | 'postData' => $request->request->all(), 65 | 'headers' => $request->headers->all(), 66 | 'cookies' => $request->cookies->all(), 67 | 'uri' => $request->getRequestUri(), 68 | 'method' => $request->getMethod(), 69 | 'responseStatus' => $response->getStatusCode(), 70 | ]; 71 | 72 | if ($this->session) { 73 | $sessionAttributes = []; 74 | foreach ($this->session->all() as $key => $value) { 75 | $sessionAttributes[$key] = $value; 76 | } 77 | $data['sessionData'] = $sessionAttributes; 78 | } 79 | 80 | if (isset($data['postData']['php-auth-pw'])) { 81 | $data['postData']['php-auth-pw'] = '******'; 82 | } 83 | 84 | if (isset($data['postData']['PHP_AUTH_PW'])) { 85 | $data['postData']['PHP_AUTH_PW'] = '******'; 86 | } 87 | 88 | return $data; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/DataCollector/ViewCollector.php: -------------------------------------------------------------------------------- 1 | collect_data = $collectData; 22 | $this->name = 'views'; 23 | $this->templates = []; 24 | $this->exporter = new ValueExporter(); 25 | } 26 | 27 | public function getName() 28 | { 29 | return 'views'; 30 | } 31 | 32 | public function getWidgets() 33 | { 34 | return [ 35 | 'views' => [ 36 | 'icon' => 'leaf', 37 | 'widget' => 'PhpDebugBar.Widgets.TemplatesWidget', 38 | 'map' => 'views', 39 | 'default' => '[]' 40 | ], 41 | 'views:badge' => [ 42 | 'map' => 'views.nb_templates', 43 | 'default' => 0 44 | ] 45 | ]; 46 | } 47 | 48 | /** 49 | * Add a View instance to the Collector 50 | * 51 | * @param \Illuminate\View\View $view 52 | */ 53 | public function addView(View $view) 54 | { 55 | $name = $view->getName(); 56 | $path = $view->getPath(); 57 | 58 | if (!is_object($path)) { 59 | if ($path) { 60 | $path = ltrim(str_replace(base_path(), '', realpath($path)), '/'); 61 | } 62 | 63 | if (substr($path, -10) == '.blade.php') { 64 | $type = 'blade'; 65 | } else { 66 | $type = pathinfo($path, PATHINFO_EXTENSION); 67 | } 68 | } else { 69 | $type = get_class($view); 70 | $path = ''; 71 | } 72 | 73 | if (!$this->collect_data) { 74 | $params = array_keys($view->getData()); 75 | } else { 76 | $data = []; 77 | foreach ($view->getData() as $key => $value) { 78 | $data[$key] = $this->exporter->exportValue($value); 79 | } 80 | $params = $data; 81 | } 82 | 83 | $this->templates[] = [ 84 | 'name' => $path ? sprintf('%s (%s)', $name, $path) : $name, 85 | 'param_count' => count($params), 86 | 'params' => $params, 87 | 'type' => $type, 88 | ]; 89 | } 90 | 91 | public function collect() 92 | { 93 | $templates = $this->templates; 94 | 95 | return [ 96 | 'nb_templates' => count($templates), 97 | 'templates' => $templates, 98 | ]; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/DataCollector/MultiAuthCollector.php: -------------------------------------------------------------------------------- 1 | guards = $guards; 25 | } 26 | 27 | 28 | /** 29 | * @{inheritDoc} 30 | */ 31 | public function collect() 32 | { 33 | $data = []; 34 | $names = ''; 35 | 36 | foreach($this->guards as $guardName) { 37 | $user = $this->resolveUser($this->auth->guard($guardName)); 38 | 39 | $data['guards'][$guardName] = $this->getUserInformation($user); 40 | 41 | if(!is_null($user)) { 42 | $names .= $guardName . ": " . $data['guards'][$guardName]['name'] . ', '; 43 | } 44 | } 45 | 46 | foreach ($data['guards'] as $key => $var) { 47 | if (!is_string($data['guards'][$key])) { 48 | $data['guards'][$key] = $this->formatVar($var); 49 | } 50 | } 51 | 52 | $data['names'] = rtrim($names, ', '); 53 | 54 | return $data; 55 | } 56 | 57 | private function resolveUser(Guard $guard) 58 | { 59 | // if we're logging in using remember token 60 | // then we must resolve user „manually” 61 | // to prevent csrf token regeneration 62 | 63 | $recaller = $guard instanceof SessionGuard 64 | ? $guard->getRequest()->cookies->get($guard->getRecallerName()) 65 | : null; 66 | 67 | if (is_string($recaller) && Str::contains($recaller, '|')) { 68 | $segments = explode('|', $recaller); 69 | if (count($segments) == 2 && trim($segments[0]) !== '' && trim($segments[1]) !== '') { 70 | return $guard->getProvider()->retrieveByToken($segments[0], $segments[1]); 71 | } 72 | } 73 | return $guard->user(); 74 | } 75 | 76 | /** 77 | * @{inheritDoc} 78 | */ 79 | public function getWidgets() 80 | { 81 | $widgets = [ 82 | "auth" => [ 83 | "icon" => "lock", 84 | "widget" => "PhpDebugBar.Widgets.VariableListWidget", 85 | "map" => "auth.guards", 86 | "default" => "{}" 87 | ] 88 | ]; 89 | 90 | if ($this->showName) { 91 | $widgets['auth.name'] = [ 92 | 'icon' => 'user', 93 | 'tooltip' => 'Auth status', 94 | 'map' => 'auth.names', 95 | 'default' => '', 96 | ]; 97 | } 98 | 99 | return $widgets; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/DataCollector/AuthCollector.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 25 | } 26 | 27 | /** 28 | * Set to show the users name/email 29 | * @param bool $showName 30 | */ 31 | public function setShowName($showName) 32 | { 33 | $this->showName = (bool) $showName; 34 | } 35 | 36 | /** 37 | * @{inheritDoc} 38 | */ 39 | public function collect() 40 | { 41 | try { 42 | $user = $this->auth->user(); 43 | } catch (\Exception $e) { 44 | $user = null; 45 | } 46 | return $this->getUserInformation($user); 47 | } 48 | 49 | /** 50 | * Get displayed user information 51 | * @param \Illuminate\Auth\UserInterface $user 52 | * @return array 53 | */ 54 | protected function getUserInformation($user = null) 55 | { 56 | // Defaults 57 | if (is_null($user)) { 58 | return [ 59 | 'name' => 'Guest', 60 | 'user' => ['guest' => true], 61 | ]; 62 | } 63 | 64 | // The default auth identifer is the ID number, which isn't all that 65 | // useful. Try username and email. 66 | $identifier = $user->getAuthIdentifier(); 67 | if (is_numeric($identifier)) { 68 | try { 69 | if ($user->username) { 70 | $identifier = $user->username; 71 | } elseif ($user->email) { 72 | $identifier = $user->email; 73 | } 74 | } catch (\Exception $e) { 75 | } 76 | } 77 | 78 | return [ 79 | 'name' => $identifier, 80 | 'user' => $user instanceof Arrayable ? $user->toArray() : $user, 81 | ]; 82 | } 83 | 84 | /** 85 | * @{inheritDoc} 86 | */ 87 | public function getName() 88 | { 89 | return 'auth'; 90 | } 91 | 92 | /** 93 | * @{inheritDoc} 94 | */ 95 | public function getWidgets() 96 | { 97 | $widgets = [ 98 | 'auth' => [ 99 | 'icon' => 'lock', 100 | 'widget' => 'PhpDebugBar.Widgets.VariableListWidget', 101 | 'map' => 'auth.user', 102 | 'default' => '{}' 103 | ] 104 | ]; 105 | if ($this->showName) { 106 | $widgets['auth.name'] = [ 107 | 'icon' => 'user', 108 | 'tooltip' => 'Auth status', 109 | 'map' => 'auth.name', 110 | 'default' => '', 111 | ]; 112 | } 113 | return $widgets; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/JavascriptRenderer.php: -------------------------------------------------------------------------------- 1 | cssFiles['laravel'] = __DIR__ . '/Resources/laravel-debugbar.css'; 21 | $this->cssVendors['fontawesome'] = __DIR__ . '/Resources/vendor/font-awesome/style.css'; 22 | $this->jsFiles['laravel-sql'] = __DIR__ . '/Resources/sqlqueries/widget.js'; 23 | } 24 | 25 | /** 26 | * Set the URL Generator 27 | * 28 | * @param \Illuminate\Routing\UrlGenerator $url 29 | * @deprecated 30 | */ 31 | public function setUrlGenerator($url) 32 | { 33 | 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function renderHead() 40 | { 41 | $cssRoute = route('debugbar.assets.css', [ 42 | 'v' => $this->getModifiedTime('css') 43 | ]); 44 | 45 | $jsRoute = route('debugbar.assets.js', [ 46 | 'v' => $this->getModifiedTime('js') 47 | ]); 48 | 49 | $cssRoute = preg_replace('/\Ahttps?:/', '', $cssRoute); 50 | $jsRoute = preg_replace('/\Ahttps?:/', '', $jsRoute); 51 | 52 | $html = ""; 53 | $html .= ""; 54 | 55 | if ($this->isJqueryNoConflictEnabled()) { 56 | $html .= '' . "\n"; 57 | } 58 | 59 | return $html; 60 | } 61 | 62 | /** 63 | * Get the last modified time of any assets. 64 | * 65 | * @param string $type 'js' or 'css' 66 | * @return int 67 | */ 68 | protected function getModifiedTime($type) 69 | { 70 | $files = $this->getAssets($type); 71 | 72 | $latest = 0; 73 | foreach ($files as $file) { 74 | $mtime = filemtime($file); 75 | if ($mtime > $latest) { 76 | $latest = $mtime; 77 | } 78 | } 79 | return $latest; 80 | } 81 | 82 | /** 83 | * Return assets as a string 84 | * 85 | * @param string $type 'js' or 'css' 86 | * @return string 87 | */ 88 | public function dumpAssetsToString($type) 89 | { 90 | $files = $this->getAssets($type); 91 | 92 | $content = ''; 93 | foreach ($files as $file) { 94 | $content .= file_get_contents($file) . "\n"; 95 | } 96 | 97 | return $content; 98 | } 99 | 100 | /** 101 | * Makes a URI relative to another 102 | * 103 | * @param string|array $uri 104 | * @param string $root 105 | * @return string 106 | */ 107 | protected function makeUriRelativeTo($uri, $root) 108 | { 109 | if (!$root) { 110 | return $uri; 111 | } 112 | 113 | if (is_array($uri)) { 114 | $uris = []; 115 | foreach ($uri as $u) { 116 | $uris[] = $this->makeUriRelativeTo($u, $root); 117 | } 118 | return $uris; 119 | } 120 | 121 | if (substr($uri, 0, 1) === '/' || preg_match('/^([a-zA-Z]+:\/\/|[a-zA-Z]:\/|[a-zA-Z]:\\\)/', $uri)) { 122 | return $uri; 123 | } 124 | return rtrim($root, '/') . "/$uri"; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/DataCollector/EventCollector.php: -------------------------------------------------------------------------------- 1 | exporter = new ValueExporter(); 22 | } 23 | 24 | public function onWildcardEvent($name = null, $data = []) 25 | { 26 | // Pre-Laravel 5.4, using 'firing' to get the current event name. 27 | if (method_exists($this->events, 'firing')) { 28 | $name = $this->events->firing(); 29 | 30 | // Get the arguments passed to the event 31 | $data = func_get_args(); 32 | } 33 | 34 | $params = $this->prepareParams($data); 35 | $time = microtime(true); 36 | 37 | // Find all listeners for the current event 38 | foreach ($this->events->getListeners($name) as $i => $listener) { 39 | 40 | // Check if it's an object + method name 41 | if (is_array($listener) && count($listener) > 1 && is_object($listener[0])) { 42 | list($class, $method) = $listener; 43 | 44 | // Skip this class itself 45 | if ($class instanceof static) { 46 | continue; 47 | } 48 | 49 | // Format the listener to readable format 50 | $listener = get_class($class) . '@' . $method; 51 | 52 | // Handle closures 53 | } elseif ($listener instanceof \Closure) { 54 | $reflector = new \ReflectionFunction($listener); 55 | 56 | // Skip our own listeners 57 | if ($reflector->getNamespaceName() == 'Barryvdh\Debugbar') { 58 | continue; 59 | } 60 | 61 | // Format the closure to a readable format 62 | $filename = ltrim(str_replace(base_path(), '', $reflector->getFileName()), '/'); 63 | $listener = $reflector->getName() . ' (' . $filename . ':' . $reflector->getStartLine() . '-' . $reflector->getEndLine() . ')'; 64 | } else { 65 | // Not sure if this is possible, but to prevent edge cases 66 | $listener = $this->formatVar($listener); 67 | } 68 | 69 | $params['listeners.' . $i] = $listener; 70 | } 71 | $this->addMeasure($name, $time, $time, $params); 72 | } 73 | 74 | public function subscribe(Dispatcher $events) 75 | { 76 | $this->events = $events; 77 | $events->listen('*', [$this, 'onWildcardEvent']); 78 | } 79 | 80 | protected function prepareParams($params) 81 | { 82 | $data = []; 83 | foreach ($params as $key => $value) { 84 | if (is_object($value) && Str::is('Illuminate\*\Events\*', get_class($value))) { 85 | $value = $this->prepareParams(get_object_vars($value)); 86 | } 87 | $data[$key] = htmlentities($this->exporter->exportValue($value), ENT_QUOTES, 'UTF-8', false); 88 | } 89 | 90 | return $data; 91 | } 92 | 93 | public function collect() 94 | { 95 | $data = parent::collect(); 96 | $data['nb_measures'] = count($data['measures']); 97 | 98 | return $data; 99 | } 100 | 101 | public function getName() 102 | { 103 | return 'event'; 104 | } 105 | 106 | public function getWidgets() 107 | { 108 | return [ 109 | "events" => [ 110 | "icon" => "tasks", 111 | "widget" => "PhpDebugBar.Widgets.TimelineWidget", 112 | "map" => "event", 113 | "default" => "{}", 114 | ], 115 | 'events:badge' => [ 116 | 'map' => 'event.nb_measures', 117 | 'default' => 0, 118 | ], 119 | ]; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/DataCollector/IlluminateRouteCollector.php: -------------------------------------------------------------------------------- 1 | router = $router; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function collect() 35 | { 36 | $route = $this->router->current(); 37 | return $this->getRouteInformation($route); 38 | } 39 | 40 | /** 41 | * Get the route information for a given route. 42 | * 43 | * @param \Illuminate\Routing\Route $route 44 | * @return array 45 | */ 46 | protected function getRouteInformation($route) 47 | { 48 | if (!is_a($route, 'Illuminate\Routing\Route')) { 49 | return []; 50 | } 51 | $uri = head($route->methods()) . ' ' . $route->uri(); 52 | $action = $route->getAction(); 53 | 54 | $result = [ 55 | 'uri' => $uri ?: '-', 56 | ]; 57 | 58 | $result = array_merge($result, $action); 59 | 60 | 61 | if (isset($action['controller']) && strpos($action['controller'], '@') !== false) { 62 | list($controller, $method) = explode('@', $action['controller']); 63 | if(class_exists($controller) && method_exists($controller, $method)) { 64 | $reflector = new \ReflectionMethod($controller, $method); 65 | } 66 | unset($result['uses']); 67 | } elseif (isset($action['uses']) && $action['uses'] instanceof \Closure) { 68 | $reflector = new \ReflectionFunction($action['uses']); 69 | $result['uses'] = $this->formatVar($result['uses']); 70 | } 71 | 72 | if (isset($reflector)) { 73 | $filename = ltrim(str_replace(base_path(), '', $reflector->getFileName()), '/'); 74 | $result['file'] = $filename . ':' . $reflector->getStartLine() . '-' . $reflector->getEndLine(); 75 | } 76 | 77 | if ($middleware = $this->getMiddleware($route)) { 78 | $result['middleware'] = $middleware; 79 | } 80 | 81 | 82 | 83 | return $result; 84 | } 85 | 86 | /** 87 | * Get middleware 88 | * 89 | * @param \Illuminate\Routing\Route $route 90 | * @return string 91 | */ 92 | protected function getMiddleware($route) 93 | { 94 | return implode(', ', $route->middleware()); 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | */ 100 | public function getName() 101 | { 102 | return 'route'; 103 | } 104 | 105 | /** 106 | * {@inheritDoc} 107 | */ 108 | public function getWidgets() 109 | { 110 | $widgets = [ 111 | "route" => [ 112 | "icon" => "share", 113 | "widget" => "PhpDebugBar.Widgets.VariableListWidget", 114 | "map" => "route", 115 | "default" => "{}" 116 | ] 117 | ]; 118 | if (Config::get('debugbar.options.route.label', true)) { 119 | $widgets['currentroute'] = [ 120 | "icon" => "share", 121 | "tooltip" => "Route", 122 | "map" => "route.uri", 123 | "default" => "" 124 | ]; 125 | } 126 | return $widgets; 127 | } 128 | 129 | /** 130 | * Display the route information on the console. 131 | * 132 | * @param array $routes 133 | * @return void 134 | */ 135 | protected function displayRoutes(array $routes) 136 | { 137 | $this->table->setHeaders($this->headers)->setRows($routes); 138 | 139 | $this->table->render($this->getOutput()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/DataCollector/LogsCollector.php: -------------------------------------------------------------------------------- 1 | getLogsFile(); 17 | $this->getStorageLogs($path); 18 | } 19 | 20 | /** 21 | * Get the path to the logs file 22 | * 23 | * @return string 24 | */ 25 | public function getLogsFile() 26 | { 27 | // default daily rotating logs (Laravel 5.0) 28 | $path = storage_path() . '/logs/laravel-' . date('Y-m-d') . '.log'; 29 | 30 | // single file logs 31 | if (!file_exists($path)) { 32 | $path = storage_path() . '/logs/laravel.log'; 33 | } 34 | 35 | return $path; 36 | } 37 | 38 | /** 39 | * get logs apache in app/storage/logs 40 | * only 24 last of current day 41 | * 42 | * @param string $path 43 | * 44 | * @return array 45 | */ 46 | public function getStorageLogs($path) 47 | { 48 | if (!file_exists($path)) { 49 | return; 50 | } 51 | 52 | //Load the latest lines, guessing about 15x the number of log entries (for stack traces etc) 53 | $file = implode("", $this->tailFile($path, $this->lines)); 54 | 55 | foreach ($this->getLogs($file) as $log) { 56 | $this->addMessage($log['header'] . $log['stack'], $log['level'], false); 57 | } 58 | } 59 | 60 | /** 61 | * By Ain Tohvri (ain) 62 | * http://tekkie.flashbit.net/php/tail-functionality-in-php 63 | * @param string $file 64 | * @param int $lines 65 | * @return array 66 | */ 67 | protected function tailFile($file, $lines) 68 | { 69 | $handle = fopen($file, "r"); 70 | $linecounter = $lines; 71 | $pos = -2; 72 | $beginning = false; 73 | $text = []; 74 | while ($linecounter > 0) { 75 | $t = " "; 76 | while ($t != "\n") { 77 | if (fseek($handle, $pos, SEEK_END) == -1) { 78 | $beginning = true; 79 | break; 80 | } 81 | $t = fgetc($handle); 82 | $pos--; 83 | } 84 | $linecounter--; 85 | if ($beginning) { 86 | rewind($handle); 87 | } 88 | $text[$lines - $linecounter - 1] = fgets($handle); 89 | if ($beginning) { 90 | break; 91 | } 92 | } 93 | fclose($handle); 94 | return array_reverse($text); 95 | } 96 | 97 | /** 98 | * Search a string for log entries 99 | * Based on https://github.com/mikemand/logviewer/blob/master/src/Kmd/Logviewer/Logviewer.php by mikemand 100 | * 101 | * @param $file 102 | * @return array 103 | */ 104 | public function getLogs($file) 105 | { 106 | $pattern = "/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\].*/"; 107 | 108 | $log_levels = $this->getLevels(); 109 | 110 | // There has GOT to be a better way of doing this... 111 | preg_match_all($pattern, $file, $headings); 112 | $log_data = preg_split($pattern, $file); 113 | 114 | $log = []; 115 | foreach ($headings as $h) { 116 | for ($i = 0, $j = count($h); $i < $j; $i++) { 117 | foreach ($log_levels as $ll) { 118 | if (strpos(strtolower($h[$i]), strtolower('.' . $ll))) { 119 | $log[] = ['level' => $ll, 'header' => $h[$i], 'stack' => $log_data[$i]]; 120 | } 121 | } 122 | } 123 | } 124 | 125 | $log = array_reverse($log); 126 | 127 | return $log; 128 | } 129 | 130 | /** 131 | * Get the log levels from psr/log. 132 | * Based on https://github.com/mikemand/logviewer/blob/master/src/Kmd/Logviewer/Logviewer.php by mikemand 133 | * 134 | * @access public 135 | * @return array 136 | */ 137 | public function getLevels() 138 | { 139 | $class = new ReflectionClass(new LogLevel()); 140 | return $class->getConstants(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/DataCollector/FilesCollector.php: -------------------------------------------------------------------------------- 1 | app = $app; 21 | $this->basePath = base_path(); 22 | } 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | public function collect() 28 | { 29 | $files = $this->getIncludedFiles(); 30 | $compiled = $this->getCompiledFiles(); 31 | 32 | $included = []; 33 | $alreadyCompiled = []; 34 | foreach ($files as $file) { 35 | // Skip the files from Debugbar, they are only loaded for Debugging and confuse the output. 36 | // Of course some files are stil always loaded (ServiceProvider, Facade etc) 37 | if (strpos($file, 'vendor/maximebf/debugbar/src') !== false || strpos( 38 | $file, 39 | 'vendor/barryvdh/laravel-debugbar/src' 40 | ) !== false 41 | ) { 42 | continue; 43 | } elseif (!in_array($file, $compiled)) { 44 | $included[] = [ 45 | 'message' => "'" . $this->stripBasePath($file) . "',", 46 | // Use PHP syntax so we can copy-paste to compile config file. 47 | 'is_string' => true, 48 | ]; 49 | } else { 50 | $alreadyCompiled[] = [ 51 | 'message' => "* '" . $this->stripBasePath($file) . "',", 52 | // Mark with *, so know they are compiled anyways. 53 | 'is_string' => true, 54 | ]; 55 | } 56 | } 57 | 58 | // First the included files, then those that are going to be compiled. 59 | $messages = array_merge($included, $alreadyCompiled); 60 | 61 | return [ 62 | 'messages' => $messages, 63 | 'count' => count($included), 64 | ]; 65 | } 66 | 67 | /** 68 | * Get the files included on load. 69 | * 70 | * @return array 71 | */ 72 | protected function getIncludedFiles() 73 | { 74 | return get_included_files(); 75 | } 76 | 77 | /** 78 | * Get the files that are going to be compiled, so they aren't as important. 79 | * 80 | * @return array 81 | */ 82 | protected function getCompiledFiles() 83 | { 84 | if ($this->app && class_exists('Illuminate\Foundation\Console\OptimizeCommand')) { 85 | $reflector = new \ReflectionClass('Illuminate\Foundation\Console\OptimizeCommand'); 86 | $path = dirname($reflector->getFileName()) . '/Optimize/config.php'; 87 | 88 | if (file_exists($path)) { 89 | $app = $this->app; 90 | $core = require $path; 91 | return array_merge($core, $app['config']['compile']); 92 | } 93 | } 94 | return []; 95 | } 96 | 97 | /** 98 | * Remove the basePath from the paths, so they are relative to the base 99 | * 100 | * @param $path 101 | * @return string 102 | */ 103 | protected function stripBasePath($path) 104 | { 105 | return ltrim(str_replace($this->basePath, '', $path), '/'); 106 | } 107 | 108 | /** 109 | * {@inheritDoc} 110 | */ 111 | public function getWidgets() 112 | { 113 | $name = $this->getName(); 114 | return [ 115 | "$name" => [ 116 | "icon" => "files-o", 117 | "widget" => "PhpDebugBar.Widgets.MessagesWidget", 118 | "map" => "$name.messages", 119 | "default" => "{}" 120 | ], 121 | "$name:badge" => [ 122 | "map" => "$name.count", 123 | "default" => "null" 124 | ] 125 | ]; 126 | } 127 | 128 | /** 129 | * {@inheritDoc} 130 | */ 131 | public function getName() 132 | { 133 | return 'files'; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Storage/FilesystemStorage.php: -------------------------------------------------------------------------------- 1 | files = $files; 26 | $this->dirname = rtrim($dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; 27 | } 28 | 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public function save($id, $data) 33 | { 34 | if (!$this->files->isDirectory($this->dirname)) { 35 | if ($this->files->makeDirectory($this->dirname, 0777, true)) { 36 | $this->files->put($this->dirname . '.gitignore', "*\n!.gitignore"); 37 | } else { 38 | throw new \Exception("Cannot create directory '$this->dirname'.."); 39 | } 40 | } 41 | 42 | try { 43 | $this->files->put($this->makeFilename($id), json_encode($data)); 44 | } catch (\Exception $e) { 45 | //TODO; error handling 46 | } 47 | 48 | // Randomly check if we should collect old files 49 | if (rand(1, 100) <= $this->gc_probability) { 50 | $this->garbageCollect(); 51 | } 52 | } 53 | 54 | /** 55 | * Create the filename for the data, based on the id. 56 | * 57 | * @param $id 58 | * @return string 59 | */ 60 | public function makeFilename($id) 61 | { 62 | return $this->dirname . basename($id) . ".json"; 63 | } 64 | 65 | /** 66 | * Delete files older then a certain age (gc_lifetime) 67 | */ 68 | protected function garbageCollect() 69 | { 70 | foreach (Finder::create()->files()->name('*.json')->date('< ' . $this->gc_lifetime . ' hour ago')->in( 71 | $this->dirname 72 | ) as $file) { 73 | $this->files->delete($file->getRealPath()); 74 | } 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | public function get($id) 81 | { 82 | return json_decode($this->files->get($this->makeFilename($id)), true); 83 | } 84 | 85 | /** 86 | * {@inheritDoc} 87 | */ 88 | public function find(array $filters = [], $max = 20, $offset = 0) 89 | { 90 | // Sort by modified time, newest first 91 | $sort = function (\SplFileInfo $a, \SplFileInfo $b) { 92 | return strcmp($b->getMTime(), $a->getMTime()); 93 | }; 94 | 95 | // Loop through .json files, filter the metadata and stop when max is found. 96 | $i = 0; 97 | $results = []; 98 | foreach (Finder::create()->files()->name('*.json')->in($this->dirname)->sort($sort) as $file) { 99 | if ($i++ < $offset && empty($filters)) { 100 | $results[] = null; 101 | continue; 102 | } 103 | $data = json_decode($file->getContents(), true); 104 | $meta = $data['__meta']; 105 | unset($data); 106 | if ($this->filter($meta, $filters)) { 107 | $results[] = $meta; 108 | } 109 | if (count($results) >= ($max + $offset)) { 110 | break; 111 | } 112 | } 113 | return array_slice($results, $offset, $max); 114 | } 115 | 116 | /** 117 | * Filter the metadata for matches. 118 | * 119 | * @param $meta 120 | * @param $filters 121 | * @return bool 122 | */ 123 | protected function filter($meta, $filters) 124 | { 125 | foreach ($filters as $key => $value) { 126 | if (!isset($meta[$key]) || fnmatch($value, $meta[$key]) === false) { 127 | return false; 128 | } 129 | } 130 | return true; 131 | } 132 | 133 | /** 134 | * {@inheritDoc} 135 | */ 136 | public function clear() 137 | { 138 | foreach (Finder::create()->files()->name('*.json')->in($this->dirname) as $file) { 139 | $this->files->delete($file->getRealPath()); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Support/Clockwork/Converter.php: -------------------------------------------------------------------------------- 1 | $meta['id'], 18 | 'method' => $meta['method'], 19 | 'uri' => $meta['uri'], 20 | 'time' => $meta['utime'], 21 | 'headers' => [], 22 | 'cookies' => [], 23 | 'emailsData' => [], 24 | 'getData' => [], 25 | 'log' => [], 26 | 'postData' => [], 27 | 'sessionData' => [], 28 | 'timelineData' => [], 29 | 'viewsData' => [], 30 | 'controller' => null, 31 | 'responseTime' => null, 32 | 'responseStatus' => null, 33 | 'responseDuration' => 0, 34 | ]; 35 | 36 | if (isset($data['clockwork'])) { 37 | $output = array_merge($output, $data['clockwork']); 38 | } 39 | 40 | if (isset($data['time'])) { 41 | $time = $data['time']; 42 | $output['time'] = $time['start']; 43 | $output['responseTime'] = $time['end']; 44 | $output['responseDuration'] = $time['duration'] * 1000; 45 | foreach($time['measures'] as $measure) { 46 | $output['timelineData'][] = [ 47 | 'data' => [], 48 | 'description' => $measure['label'], 49 | 'duration' => $measure['duration'] * 1000, 50 | 'end' => $measure['end'], 51 | 'start' => $measure['start'], 52 | 'relative_start' => $measure['start'] - $time['start'], 53 | ]; 54 | } 55 | } 56 | 57 | if (isset($data['route'])) { 58 | $route = $data['route']; 59 | 60 | $controller = null; 61 | if (isset($route['controller'])) { 62 | $controller = $route['controller']; 63 | } elseif (isset($route['uses'])) { 64 | $controller = $route['uses']; 65 | } 66 | 67 | $output['controller'] = $controller; 68 | 69 | list($method, $uri) = explode(' ', $route['uri'], 2); 70 | 71 | $output['routes'][] = [ 72 | 'action' => $controller, 73 | 'after' => isset($route['after']) ? $route['after'] : null, 74 | 'before' => isset($route['before']) ? $route['before'] : null, 75 | 'method' => $method, 76 | 'name' => isset($route['as']) ? $route['as'] : null, 77 | 'uri' => $uri, 78 | ]; 79 | } 80 | 81 | if (isset($data['messages'])) { 82 | foreach($data['messages']['messages'] as $message) { 83 | $output['log'][] = [ 84 | 'message' => $message['message'], 85 | 'time' => $message['time'], 86 | 'level' => $message['label'], 87 | ]; 88 | } 89 | } 90 | 91 | if (isset($data['queries'])) { 92 | $queries = $data['queries']; 93 | foreach($queries['statements'] as $statement){ 94 | $output['databaseQueries'][] = [ 95 | 'query' => $statement['sql'], 96 | 'bindings' => $statement['params'], 97 | 'duration' => $statement['duration'] * 1000, 98 | 'connection' => $statement['connection'] 99 | ]; 100 | } 101 | 102 | $output['databaseDuration'] = $queries['accumulated_duration'] * 1000; 103 | } 104 | 105 | if (isset($data['views'])) { 106 | foreach ($data['views']['templates'] as $view) { 107 | $output['viewsData'][] = [ 108 | 'description' => 'Rendering a view', 109 | 'duration' => 0, 110 | 'end' => 0, 111 | 'start' => 0, 112 | 'data' => [ 113 | 'name' => $view['name'], 114 | 'data' => $view['params'], 115 | ], 116 | ]; 117 | } 118 | } 119 | 120 | if (isset($data['swiftmailer_mails'])) { 121 | foreach($data['swiftmailer_mails']['mails'] as $mail) { 122 | $output['emailsData'][] = [ 123 | 'data' => [ 124 | 'to' => $mail['to'], 125 | 'subject' => $mail['subject'], 126 | 'headers' => isset($mail['headers']) ? explode("\n", $mail['headers']) : null, 127 | ], 128 | ]; 129 | } 130 | } 131 | 132 | return $output; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom($configPath, 'debugbar'); 24 | 25 | $this->app->alias( 26 | 'DebugBar\DataFormatter\DataFormatter', 27 | 'DebugBar\DataFormatter\DataFormatterInterface' 28 | ); 29 | 30 | $this->app->singleton('debugbar', function ($app) { 31 | $debugbar = new LaravelDebugbar($app); 32 | 33 | if ($app->bound(SessionManager::class)) { 34 | $sessionManager = $app->make(SessionManager::class); 35 | $httpDriver = new SymfonyHttpDriver($sessionManager); 36 | $debugbar->setHttpDriver($httpDriver); 37 | } 38 | 39 | return $debugbar; 40 | } 41 | ); 42 | 43 | $this->app->alias('debugbar', 'Barryvdh\Debugbar\LaravelDebugbar'); 44 | 45 | $this->app->singleton('command.debugbar.clear', 46 | function ($app) { 47 | return new Console\ClearCommand($app['debugbar']); 48 | } 49 | ); 50 | 51 | $this->commands(['command.debugbar.clear']); 52 | } 53 | 54 | /** 55 | * Bootstrap the application events. 56 | * 57 | * @return void 58 | */ 59 | public function boot() 60 | { 61 | $app = $this->app; 62 | 63 | $configPath = __DIR__ . '/../config/debugbar.php'; 64 | $this->publishes([$configPath => $this->getConfigPath()], 'config'); 65 | 66 | // If enabled is null, set from the app.debug value 67 | $enabled = $this->app['config']->get('debugbar.enabled'); 68 | 69 | if (is_null($enabled)) { 70 | $enabled = $this->checkAppDebug(); 71 | } 72 | 73 | if (! $enabled) { 74 | return; 75 | } 76 | 77 | $routeConfig = [ 78 | 'namespace' => 'Barryvdh\Debugbar\Controllers', 79 | 'prefix' => $this->app['config']->get('debugbar.route_prefix'), 80 | 'domain' => $this->app['config']->get('debugbar.route_domain'), 81 | ]; 82 | 83 | $this->getRouter()->group($routeConfig, function($router) { 84 | $router->get('open', [ 85 | 'uses' => 'OpenHandlerController@handle', 86 | 'as' => 'debugbar.openhandler', 87 | ]); 88 | 89 | $router->get('clockwork/{id}', [ 90 | 'uses' => 'OpenHandlerController@clockwork', 91 | 'as' => 'debugbar.clockwork', 92 | ]); 93 | 94 | $router->get('assets/stylesheets', [ 95 | 'uses' => 'AssetController@css', 96 | 'as' => 'debugbar.assets.css', 97 | ]); 98 | 99 | $router->get('assets/javascript', [ 100 | 'uses' => 'AssetController@js', 101 | 'as' => 'debugbar.assets.js', 102 | ]); 103 | }); 104 | 105 | if ($app->runningInConsole() || $app->environment('testing')) { 106 | return; 107 | } 108 | 109 | /** @var LaravelDebugbar $debugbar */ 110 | $debugbar = $this->app['debugbar']; 111 | $debugbar->enable(); 112 | $debugbar->boot(); 113 | 114 | $this->registerMiddleware('Barryvdh\Debugbar\Middleware\Debugbar'); 115 | } 116 | 117 | /** 118 | * Get the active router. 119 | * 120 | * @return Router 121 | */ 122 | protected function getRouter() 123 | { 124 | return $this->app['router']; 125 | } 126 | 127 | /** 128 | * Get the config path 129 | * 130 | * @return string 131 | */ 132 | protected function getConfigPath() 133 | { 134 | return config_path('debugbar.php'); 135 | } 136 | 137 | /** 138 | * Publish the config file 139 | * 140 | * @param string $configPath 141 | */ 142 | protected function publishConfig($configPath) 143 | { 144 | $this->publishes([$configPath => config_path('debugbar.php')], 'config'); 145 | } 146 | 147 | /** 148 | * Register the Debugbar Middleware 149 | * 150 | * @param string $middleware 151 | */ 152 | protected function registerMiddleware($middleware) 153 | { 154 | $kernel = $this->app['Illuminate\Contracts\Http\Kernel']; 155 | $kernel->pushMiddleware($middleware); 156 | } 157 | 158 | /** 159 | * Check the App Debug status 160 | */ 161 | protected function checkAppDebug() 162 | { 163 | return $this->app['config']->get('app.debug'); 164 | } 165 | 166 | /** 167 | * Get the services provided by the provider. 168 | * 169 | * @return array 170 | */ 171 | public function provides() 172 | { 173 | return ['debugbar', 'command.debugbar.clear']; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/DataCollector/SymfonyRequestCollector.php: -------------------------------------------------------------------------------- 1 | 13 | * 14 | */ 15 | class SymfonyRequestCollector extends DataCollector implements DataCollectorInterface, Renderable 16 | { 17 | /** @var \Symfony\Component\HttpFoundation\Request $request */ 18 | protected $request; 19 | /** @var \Symfony\Component\HttpFoundation\Request $response */ 20 | protected $response; 21 | /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */ 22 | protected $session; 23 | 24 | /** 25 | * Create a new SymfonyRequestCollector 26 | * 27 | * @param \Symfony\Component\HttpFoundation\Request $request 28 | * @param \Symfony\Component\HttpFoundation\Request $response 29 | * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session 30 | */ 31 | public function __construct($request, $response, $session = null) 32 | { 33 | $this->request = $request; 34 | $this->response = $response; 35 | $this->session = $session; 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | public function getName() 42 | { 43 | return 'request'; 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public function getWidgets() 50 | { 51 | return [ 52 | "request" => [ 53 | "icon" => "tags", 54 | "widget" => "PhpDebugBar.Widgets.VariableListWidget", 55 | "map" => "request", 56 | "default" => "{}" 57 | ] 58 | ]; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function collect() 65 | { 66 | $request = $this->request; 67 | $response = $this->response; 68 | 69 | $responseHeaders = $response->headers->all(); 70 | $cookies = []; 71 | foreach ($response->headers->getCookies() as $cookie) { 72 | $cookies[] = $this->getCookieHeader( 73 | $cookie->getName(), 74 | $cookie->getValue(), 75 | $cookie->getExpiresTime(), 76 | $cookie->getPath(), 77 | $cookie->getDomain(), 78 | $cookie->isSecure(), 79 | $cookie->isHttpOnly() 80 | ); 81 | } 82 | if (count($cookies) > 0) { 83 | $responseHeaders['Set-Cookie'] = $cookies; 84 | } 85 | 86 | $statusCode = $response->getStatusCode(); 87 | 88 | $data = [ 89 | 'format' => $request->getRequestFormat(), 90 | 'content_type' => $response->headers->get('Content-Type') ? $response->headers->get( 91 | 'Content-Type' 92 | ) : 'text/html', 93 | 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', 94 | 'status_code' => $statusCode, 95 | 'request_query' => $request->query->all(), 96 | 'request_request' => $request->request->all(), 97 | 'request_headers' => $request->headers->all(), 98 | 'request_server' => $request->server->all(), 99 | 'request_cookies' => $request->cookies->all(), 100 | 'response_headers' => $responseHeaders, 101 | 'path_info' => $request->getPathInfo(), 102 | ]; 103 | 104 | if ($this->session) { 105 | $sessionAttributes = []; 106 | foreach ($this->session->all() as $key => $value) { 107 | $sessionAttributes[$key] = $value; 108 | } 109 | $data['session_attributes'] = $sessionAttributes; 110 | } 111 | 112 | foreach ($data['request_server'] as $key => $value) { 113 | if (str_is('*_KEY', $key) || str_is('*_PASSWORD', $key) 114 | || str_is('*_SECRET', $key) || str_is('*_PW', $key)) { 115 | $data['request_server'][$key] = '******'; 116 | } 117 | } 118 | 119 | if (isset($data['request_headers']['php-auth-pw'])) { 120 | $data['request_headers']['php-auth-pw'] = '******'; 121 | } 122 | 123 | if (isset($data['request_server']['PHP_AUTH_PW'])) { 124 | $data['request_server']['PHP_AUTH_PW'] = '******'; 125 | } 126 | 127 | foreach ($data as $key => $var) { 128 | if (!is_string($data[$key])) { 129 | $data[$key] = $this->formatVar($var); 130 | } 131 | } 132 | 133 | return $data; 134 | } 135 | 136 | private function getCookieHeader($name, $value, $expires, $path, $domain, $secure, $httponly) 137 | { 138 | $cookie = sprintf('%s=%s', $name, urlencode($value)); 139 | 140 | if (0 !== $expires) { 141 | if (is_numeric($expires)) { 142 | $expires = (int) $expires; 143 | } elseif ($expires instanceof \DateTime) { 144 | $expires = $expires->getTimestamp(); 145 | } else { 146 | $expires = strtotime($expires); 147 | if (false === $expires || -1 == $expires) { 148 | throw new \InvalidArgumentException( 149 | sprintf('The "expires" cookie parameter is not valid.', $expires) 150 | ); 151 | } 152 | } 153 | 154 | $cookie .= '; expires=' . substr( 155 | \DateTime::createFromFormat('U', $expires, new \DateTimeZone('UTC'))->format('D, d-M-Y H:i:s T'), 156 | 0, 157 | -5 158 | ); 159 | } 160 | 161 | if ($domain) { 162 | $cookie .= '; domain=' . $domain; 163 | } 164 | 165 | $cookie .= '; path=' . $path; 166 | 167 | if ($secure) { 168 | $cookie .= '; secure'; 169 | } 170 | 171 | if ($httponly) { 172 | $cookie .= '; httponly'; 173 | } 174 | 175 | return $cookie; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Laravel Debugbar 2 | [![Packagist License](https://poser.pugx.org/barryvdh/laravel-debugbar/license.png)](http://choosealicense.com/licenses/mit/) 3 | [![Latest Stable Version](https://poser.pugx.org/barryvdh/laravel-debugbar/version.png)](https://packagist.org/packages/barryvdh/laravel-debugbar) 4 | [![Total Downloads](https://poser.pugx.org/barryvdh/laravel-debugbar/d/total.png)](https://packagist.org/packages/barryvdh/laravel-debugbar) 5 | 6 | ### For Laravel 4, please use the [1.8 branch](https://github.com/barryvdh/laravel-debugbar/tree/1.8)! 7 | 8 | This is a package to integrate [PHP Debug Bar](http://phpdebugbar.com/) with Laravel 5. 9 | It includes a ServiceProvider to register the debugbar and attach it to the output. You can publish assets and configure it through Laravel. 10 | It bootstraps some Collectors to work with Laravel and implements a couple custom DataCollectors, specific for Laravel. 11 | It is configured to display Redirects and (jQuery) Ajax Requests. (Shown in a dropdown) 12 | Read [the documentation](http://phpdebugbar.com/docs/) for more configuration options. 13 | 14 | ![Screenshot](https://cloud.githubusercontent.com/assets/973269/4270452/740c8c8c-3ccb-11e4-8d9a-5a9e64f19351.png) 15 | 16 | Note: Use the DebugBar only in development. It can slow the application down (because it has to gather data). So when experiencing slowness, try disabling some of the collectors. 17 | 18 | This package includes some custom collectors: 19 | - QueryCollector: Show all queries, including binding + timing 20 | - RouteCollector: Show information about the current Route. 21 | - ViewCollector: Show the currently loaded views. (Optionally: display the shared data) 22 | - EventsCollector: Show all events 23 | - LaravelCollector: Show the Laravel version and Environment. (disabled by default) 24 | - SymfonyRequestCollector: replaces the RequestCollector with more information about the request/response 25 | - LogsCollector: Show the latest log entries from the storage logs. (disabled by default) 26 | - FilesCollector: Show the files that are included/required by PHP. (disabled by default) 27 | - ConfigCollector: Display the values from the config files. (disabled by default) 28 | 29 | Bootstraps the following collectors for Laravel: 30 | - LogCollector: Show all Log messages 31 | - SwiftMailCollector and SwiftLogCollector for Mail 32 | 33 | And the default collectors: 34 | - PhpInfoCollector 35 | - MessagesCollector 36 | - TimeDataCollector (With Booting and Application timing) 37 | - MemoryCollector 38 | - ExceptionsCollector 39 | 40 | It also provides a Facade interface for easy logging Messages, Exceptions and Time 41 | 42 | ## Installation 43 | 44 | Require this package with composer: 45 | 46 | ```shell 47 | composer require barryvdh/laravel-debugbar 48 | ``` 49 | 50 | After updating composer, add the ServiceProvider to the providers array in config/app.php 51 | 52 | > Laravel 5.5 uses Package Auto-Discovery, so doesn't require you to manually add the ServiceProvider 53 | 54 | > If you use a catch-all/fallback route, make sure you load the Debugbar ServiceProvider before your own App ServiceProviders. 55 | 56 | ### Laravel 5.x: 57 | 58 | ```php 59 | Barryvdh\Debugbar\ServiceProvider::class, 60 | ``` 61 | 62 | If you want to use the facade to log messages, add this to your facades in app.php: 63 | 64 | ```php 65 | 'Debugbar' => Barryvdh\Debugbar\Facade::class, 66 | ``` 67 | 68 | The profiler is enabled by default, if you have app.debug=true. You can override that in the config (`debugbar.enabled`). See more options in `config/debugbar.php` 69 | You can also set in your config if you want to include/exclude the vendor files also (FontAwesome, Highlight.js and jQuery). If you already use them in your site, set it to false. 70 | You can also only display the js or css vendors, by setting it to 'js' or 'css'. (Highlight.js requires both css + js, so set to `true` for syntax highlighting) 71 | 72 | Copy the package config to your local config with the publish command: 73 | 74 | ```shell 75 | php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider" 76 | ``` 77 | 78 | ### Lumen: 79 | 80 | For Lumen, register a different Provider in `bootstrap/app.php`: 81 | 82 | ```php 83 | if (env('APP_DEBUG')) { 84 | $app->register(Barryvdh\Debugbar\LumenServiceProvider::class); 85 | } 86 | ``` 87 | 88 | To change the configuration, copy the file to your config folder and enable it: 89 | 90 | ```php 91 | $app->configure('debugbar'); 92 | ``` 93 | 94 | ## Usage 95 | 96 | You can now add messages using the Facade (when added), using the PSR-3 levels (debug, info, notice, warning, error, critical, alert, emergency): 97 | 98 | ```php 99 | Debugbar::info($object); 100 | Debugbar::error('Error!'); 101 | Debugbar::warning('Watch out…'); 102 | Debugbar::addMessage('Another message', 'mylabel'); 103 | ``` 104 | 105 | And start/stop timing: 106 | 107 | ```php 108 | Debugbar::startMeasure('render','Time for rendering'); 109 | Debugbar::stopMeasure('render'); 110 | Debugbar::addMeasure('now', LARAVEL_START, microtime(true)); 111 | Debugbar::measure('My long operation', function() { 112 | // Do something… 113 | }); 114 | ``` 115 | 116 | Or log exceptions: 117 | 118 | ```php 119 | try { 120 | throw new Exception('foobar'); 121 | } catch (Exception $e) { 122 | Debugbar::addThrowable($e); 123 | } 124 | ``` 125 | 126 | There are also helper functions available for the most common calls: 127 | 128 | ```php 129 | // All arguments will be dumped as a debug message 130 | debug($var1, $someString, $intValue, $object); 131 | 132 | start_measure('render','Time for rendering'); 133 | stop_measure('render'); 134 | add_measure('now', LARAVEL_START, microtime(true)); 135 | measure('My long operation', function() { 136 | // Do something… 137 | }); 138 | ``` 139 | 140 | If you want you can add your own DataCollectors, through the Container or the Facade: 141 | 142 | ```php 143 | Debugbar::addCollector(new DebugBar\DataCollector\MessagesCollector('my_messages')); 144 | //Or via the App container: 145 | $debugbar = App::make('debugbar'); 146 | $debugbar->addCollector(new DebugBar\DataCollector\MessagesCollector('my_messages')); 147 | ``` 148 | 149 | By default, the Debugbar is injected just before ``. If you want to inject the Debugbar yourself, 150 | set the config option 'inject' to false and use the renderer yourself and follow http://phpdebugbar.com/docs/rendering.html 151 | 152 | ```php 153 | $renderer = Debugbar::getJavascriptRenderer(); 154 | ``` 155 | 156 | Note: Not using the auto-inject, will disable the Request information, because that is added After the response. 157 | You can add the default_request datacollector in the config as alternative. 158 | 159 | ## Enabling/Disabling on run time 160 | You can enable or disable the debugbar during run time. 161 | 162 | ```php 163 | \Debugbar::enable(); 164 | \Debugbar::disable(); 165 | ``` 166 | 167 | NB. Once enabled, the collectors are added (and could produce extra overhead), so if you want to use the debugbar in production, disable in the config and only enable when needed. 168 | 169 | 170 | ## Twig Integration 171 | 172 | Laravel Debugbar comes with two Twig Extensions. These are tested with [rcrowe/TwigBridge](https://github.com/rcrowe/TwigBridge) 0.6.x 173 | 174 | Add the following extensions to your TwigBridge config/extensions.php (or register the extensions manually) 175 | 176 | ```php 177 | 'Barryvdh\Debugbar\Twig\Extension\Debug', 178 | 'Barryvdh\Debugbar\Twig\Extension\Dump', 179 | 'Barryvdh\Debugbar\Twig\Extension\Stopwatch', 180 | ``` 181 | 182 | The Dump extension will replace the [dump function](http://twig.sensiolabs.org/doc/functions/dump.html) to output variables using the DataFormatter. The Debug extension adds a `debug()` function which passes variables to the Message Collector, 183 | instead of showing it directly in the template. It dumps the arguments, or when empty; all context variables. 184 | 185 | ```twig 186 | {{ debug() }} 187 | {{ debug(user, categories) }} 188 | ``` 189 | 190 | The Stopwatch extension adds a [stopwatch tag](http://symfony.com/blog/new-in-symfony-2-4-a-stopwatch-tag-for-twig) similar to the one in Symfony/Silex Twigbridge. 191 | 192 | ```twig 193 | {% stopwatch "foo" %} 194 | …some things that gets timed 195 | {% endstopwatch %} 196 | ``` 197 | -------------------------------------------------------------------------------- /config/debugbar.php: -------------------------------------------------------------------------------- 1 | env('DEBUGBAR_ENABLED', null), 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Storage settings 20 | |-------------------------------------------------------------------------- 21 | | 22 | | DebugBar stores data for session/ajax requests. 23 | | You can disable this, so the debugbar stores data in headers/session, 24 | | but this can cause problems with large data collectors. 25 | | By default, file storage (in the storage folder) is used. Redis and PDO 26 | | can also be used. For PDO, run the package migrations first. 27 | | 28 | */ 29 | 'storage' => [ 30 | 'enabled' => true, 31 | 'driver' => 'file', // redis, file, pdo, custom 32 | 'path' => storage_path('debugbar'), // For file driver 33 | 'connection' => null, // Leave null for default connection (Redis/PDO) 34 | 'provider' => '' // Instance of StorageInterface for custom driver 35 | ], 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Vendors 40 | |-------------------------------------------------------------------------- 41 | | 42 | | Vendor files are included by default, but can be set to false. 43 | | This can also be set to 'js' or 'css', to only include javascript or css vendor files. 44 | | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) 45 | | and for js: jquery and and highlight.js 46 | | So if you want syntax highlighting, set it to true. 47 | | jQuery is set to not conflict with existing jQuery scripts. 48 | | 49 | */ 50 | 51 | 'include_vendors' => true, 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Capture Ajax Requests 56 | |-------------------------------------------------------------------------- 57 | | 58 | | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), 59 | | you can use this option to disable sending the data through the headers. 60 | | 61 | | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools. 62 | */ 63 | 64 | 'capture_ajax' => true, 65 | 'add_ajax_timing' => false, 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Custom Error Handler for Deprecated warnings 70 | |-------------------------------------------------------------------------- 71 | | 72 | | When enabled, the Debugbar shows deprecated warnings for Symfony components 73 | | in the Messages tab. 74 | | 75 | */ 76 | 'error_handler' => false, 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Clockwork integration 81 | |-------------------------------------------------------------------------- 82 | | 83 | | The Debugbar can emulate the Clockwork headers, so you can use the Chrome 84 | | Extension, without the server-side code. It uses Debugbar collectors instead. 85 | | 86 | */ 87 | 'clockwork' => false, 88 | 89 | /* 90 | |-------------------------------------------------------------------------- 91 | | DataCollectors 92 | |-------------------------------------------------------------------------- 93 | | 94 | | Enable/disable DataCollectors 95 | | 96 | */ 97 | 98 | 'collectors' => [ 99 | 'phpinfo' => true, // Php version 100 | 'messages' => true, // Messages 101 | 'time' => true, // Time Datalogger 102 | 'memory' => true, // Memory usage 103 | 'exceptions' => true, // Exception displayer 104 | 'log' => true, // Logs from Monolog (merged in messages if enabled) 105 | 'db' => true, // Show database (PDO) queries and bindings 106 | 'views' => true, // Views with their data 107 | 'route' => true, // Current route information 108 | 'auth' => true, // Display Laravel authentication status 109 | 'gate' => true, // Display Laravel Gate checks 110 | 'session' => true, // Display session data 111 | 'symfony_request' => true, // Only one can be enabled.. 112 | 'mail' => true, // Catch mail messages 113 | 'laravel' => false, // Laravel version and environment 114 | 'events' => false, // All events fired 115 | 'default_request' => false, // Regular or special Symfony request logger 116 | 'logs' => false, // Add the latest log messages 117 | 'files' => false, // Show the included files 118 | 'config' => false, // Display config settings 119 | ], 120 | 121 | /* 122 | |-------------------------------------------------------------------------- 123 | | Extra options 124 | |-------------------------------------------------------------------------- 125 | | 126 | | Configure some DataCollectors 127 | | 128 | */ 129 | 130 | 'options' => [ 131 | 'auth' => [ 132 | 'show_name' => true, // Also show the users name/email in the debugbar 133 | ], 134 | 'db' => [ 135 | 'with_params' => true, // Render SQL with the parameters substituted 136 | 'backtrace' => true, // Use a backtrace to find the origin of the query in your files. 137 | 'timeline' => false, // Add the queries to the timeline 138 | 'explain' => [ // Show EXPLAIN output on queries 139 | 'enabled' => false, 140 | 'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+ 141 | ], 142 | 'hints' => true, // Show hints for common mistakes 143 | ], 144 | 'mail' => [ 145 | 'full_log' => false 146 | ], 147 | 'views' => [ 148 | 'data' => false, //Note: Can slow down the application, because the data can be quite large.. 149 | ], 150 | 'route' => [ 151 | 'label' => true // show complete route on bar 152 | ], 153 | 'logs' => [ 154 | 'file' => null 155 | ], 156 | ], 157 | 158 | /* 159 | |-------------------------------------------------------------------------- 160 | | Inject Debugbar in Response 161 | |-------------------------------------------------------------------------- 162 | | 163 | | Usually, the debugbar is added just before , by listening to the 164 | | Response after the App is done. If you disable this, you have to add them 165 | | in your template yourself. See http://phpdebugbar.com/docs/rendering.html 166 | | 167 | */ 168 | 169 | 'inject' => true, 170 | 171 | /* 172 | |-------------------------------------------------------------------------- 173 | | DebugBar route prefix 174 | |-------------------------------------------------------------------------- 175 | | 176 | | Sometimes you want to set route prefix to be used by DebugBar to load 177 | | its resources from. Usually the need comes from misconfigured web server or 178 | | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 179 | | 180 | */ 181 | 'route_prefix' => '_debugbar', 182 | 183 | /* 184 | |-------------------------------------------------------------------------- 185 | | DebugBar route domain 186 | |-------------------------------------------------------------------------- 187 | | 188 | | By default DebugBar route served from the same domain that request served. 189 | | To override default domain, specify it as a non-empty value. 190 | */ 191 | 'route_domain' => null, 192 | ]; 193 | -------------------------------------------------------------------------------- /src/Resources/sqlqueries/widget.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-'); 4 | 5 | /** 6 | * Widget for the displaying sql queries 7 | * 8 | * Options: 9 | * - data 10 | */ 11 | var LaravelSQLQueriesWidget = PhpDebugBar.Widgets.LaravelSQLQueriesWidget = PhpDebugBar.Widget.extend({ 12 | 13 | className: csscls('sqlqueries'), 14 | 15 | onFilterClick: function(el) { 16 | $(el).toggleClass(csscls('excluded')); 17 | 18 | var excludedLabels = []; 19 | this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function() { 20 | excludedLabels.push(this.rel); 21 | }); 22 | 23 | this.$list.$el.find("li[connection=" + $(el).attr("rel") + "]").toggle(); 24 | 25 | this.set('exclude', excludedLabels); 26 | }, 27 | 28 | render: function() { 29 | this.$status = $('
').addClass(csscls('status')).appendTo(this.$el); 30 | 31 | this.$toolbar = $('
').addClass(csscls('toolbar')).appendTo(this.$el); 32 | 33 | var filters = [], self = this; 34 | 35 | this.$list = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, stmt) { 36 | if (stmt.type === 'transaction') { 37 | $('').addClass(csscls('sql')).addClass(csscls('name')).text(stmt.sql).appendTo(li); 38 | } else { 39 | $('').addClass(csscls('sql')).html(PhpDebugBar.Widgets.highlight(stmt.sql, 'sql')).appendTo(li); 40 | } 41 | if (stmt.duration_str) { 42 | $('').addClass(csscls('duration')).text(stmt.duration_str).appendTo(li); 43 | } 44 | if (stmt.memory_str) { 45 | $('').addClass(csscls('memory')).text(stmt.memory_str).appendTo(li); 46 | } 47 | if (typeof(stmt.row_count) != 'undefined') { 48 | $('').addClass(csscls('row-count')).text(stmt.row_count).appendTo(li); 49 | } 50 | if (typeof(stmt.stmt_id) != 'undefined' && stmt.stmt_id) { 51 | $('').addClass(csscls('stmt-id')).text(stmt.stmt_id).appendTo(li); 52 | } 53 | if (stmt.connection) { 54 | $('').addClass(csscls('database')).text(stmt.connection).appendTo(li); 55 | li.attr("connection",stmt.connection); 56 | if ( $.inArray(stmt.connection, filters) == -1 ) { 57 | filters.push(stmt.connection); 58 | $('') 59 | .addClass(csscls('filter')) 60 | .text(stmt.connection) 61 | .attr('rel', stmt.connection) 62 | .on('click', function() { self.onFilterClick(this); }) 63 | .appendTo(self.$toolbar); 64 | if (filters.length>1) { 65 | self.$toolbar.show(); 66 | self.$list.$el.css("margin-bottom","20px"); 67 | } 68 | } 69 | } 70 | if (typeof(stmt.is_success) != 'undefined' && !stmt.is_success) { 71 | li.addClass(csscls('error')); 72 | li.append($('').addClass(csscls('error')).text("[" + stmt.error_code + "] " + stmt.error_message)); 73 | } 74 | 75 | var table = $('
Metadata
').addClass(csscls('params')).appendTo(li); 76 | 77 | if (stmt.bindings && stmt.bindings.length) { 78 | table.append(function () { 79 | var icon = 'thumb-tack'; 80 | var $icon = ''; 81 | var $name = $('').addClass(csscls('name')).html('Bindings ' + $icon); 82 | var $value = $('').addClass(csscls('value')); 83 | var $span = $('').addClass('phpdebugbar-text-muted'); 84 | 85 | var index = 0; 86 | var $bindings = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, binding) { 87 | var $index = $span.clone().text(index++ + '.'); 88 | li.append($index, ' ', binding).removeClass(csscls('list-item')).addClass(csscls('table-list-item')); 89 | }}); 90 | 91 | $bindings.set('data', stmt.bindings); 92 | 93 | $bindings.$el 94 | .removeClass(csscls('list')) 95 | .addClass(csscls('table-list')) 96 | .appendTo($value); 97 | 98 | return $('').append($name, $value); 99 | }); 100 | } 101 | 102 | if (stmt.hints && stmt.hints.length) { 103 | table.append(function () { 104 | var icon = 'question-circle'; 105 | var $icon = ''; 106 | var $name = $('').addClass(csscls('name')).html('Hints ' + $icon); 107 | var $value = $('').addClass(csscls('value')); 108 | 109 | var $hints = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, hint) { 110 | li.append(hint).removeClass(csscls('list-item')).addClass(csscls('table-list-item')); 111 | }}); 112 | 113 | $hints.set('data', stmt.hints); 114 | $hints.$el 115 | .removeClass(csscls('list')) 116 | .addClass(csscls('table-list')) 117 | .appendTo($value); 118 | 119 | return $('').append($name, $value); 120 | }); 121 | } 122 | 123 | if (stmt.backtrace && stmt.backtrace.length) { 124 | table.append(function () { 125 | var icon = 'list-ul'; 126 | var $icon = ''; 127 | var $name = $('').addClass(csscls('name')).html('Backtrace ' + $icon); 128 | var $value = $('').addClass(csscls('value')); 129 | var $span = $('').addClass('phpdebugbar-text-muted'); 130 | 131 | var $backtrace = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, source) { 132 | var $parts = [ 133 | $span.clone().text(source.index + '.'), 134 | ' ', 135 | ]; 136 | 137 | if (source.namespace) { 138 | $parts.push(source.namespace + '::'); 139 | } 140 | 141 | $parts.push(source.name); 142 | $parts.push($span.clone().text(':' + source.line)); 143 | 144 | li.append($parts).removeClass(csscls('list-item')).addClass(csscls('table-list-item')); 145 | }}); 146 | 147 | $backtrace.set('data', stmt.backtrace); 148 | 149 | $backtrace.$el 150 | .removeClass(csscls('list')) 151 | .addClass(csscls('table-list')) 152 | .appendTo($value); 153 | 154 | return $('').append($name, $value); 155 | }); 156 | } 157 | 158 | if (stmt.params && !$.isEmptyObject(stmt.params)) { 159 | for (var key in stmt.params) { 160 | if (typeof stmt.params[key] !== 'function') { 161 | table.append('' + key + '' + stmt.params[key] + ''); 163 | } 164 | } 165 | } 166 | 167 | li.css('cursor', 'pointer').click(function() { 168 | if (table.is(':visible')) { 169 | table.hide(); 170 | } else { 171 | table.show(); 172 | } 173 | }); 174 | }}); 175 | this.$list.$el.appendTo(this.$el); 176 | 177 | this.bindAttr('data', function(data) { 178 | this.$list.set('data', data.statements); 179 | this.$status.empty(); 180 | var stmt; 181 | 182 | // Search for duplicate statements. 183 | for (var sql = {}, duplicate = 0, i = 0; i < data.statements.length; i++) { 184 | if(data.statements[i].type === 'query') { 185 | stmt = data.statements[i].sql; 186 | if (data.statements[i].bindings && data.statements[i].bindings.length) { 187 | stmt += JSON.stringify(data.statements[i].bindings); 188 | } 189 | sql[stmt] = sql[stmt] || { keys: [] }; 190 | sql[stmt].keys.push(i); 191 | } 192 | } 193 | // Add classes to all duplicate SQL statements. 194 | for (stmt in sql) { 195 | if (sql[stmt].keys.length > 1) { 196 | duplicate += sql[stmt].keys.length; 197 | 198 | for (i = 0; i < sql[stmt].keys.length; i++) { 199 | this.$list.$el.find('.' + csscls('list-item')).eq(sql[stmt].keys[i]) 200 | .addClass(csscls('sql-duplicate')) 201 | .addClass(csscls('sql-duplicate-'+duplicate)); 202 | } 203 | } 204 | } 205 | 206 | var t = $('').text(data.nb_statements + " statements were executed").appendTo(this.$status); 207 | if (data.nb_failed_statements) { 208 | t.append(", " + data.nb_failed_statements + " of which failed"); 209 | } 210 | if (duplicate) { 211 | t.append(", " + duplicate + " of which were duplicated"); 212 | t.append(", " + (data.nb_statements - duplicate) + " unique"); 213 | } 214 | if (data.accumulated_duration_str) { 215 | this.$status.append($('').addClass(csscls('duration')).text(data.accumulated_duration_str)); 216 | } 217 | if (data.memory_usage_str) { 218 | this.$status.append($('').addClass(csscls('memory')).text(data.memory_usage_str)); 219 | } 220 | }); 221 | } 222 | 223 | }); 224 | 225 | })(PhpDebugBar.$); 226 | -------------------------------------------------------------------------------- /src/Resources/laravel-debugbar.css: -------------------------------------------------------------------------------- 1 | div.phpdebugbar { 2 | font-size: 13px; 3 | font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 4 | direction: ltr; 5 | text-align: left; 6 | } 7 | 8 | div.phpdebugbar-resize-handle { 9 | border-bottom-color: #ddd; 10 | } 11 | 12 | div.phpdebugbar-closed, 13 | div.phpdebugbar-minimized { 14 | border-top-color: #ddd; 15 | } 16 | 17 | a.phpdebugbar-restore-btn { 18 | border-right-color: #ddd !important; 19 | } 20 | 21 | div.phpdebugbar code, div.phpdebugbar pre { 22 | background: none; 23 | font-family: monospace; 24 | font-size: 1em; 25 | border: 0; 26 | padding: 0; 27 | } 28 | 29 | div.phpdebugbar-body { 30 | border-top: none; 31 | } 32 | 33 | div.phpdebugbar-header { 34 | min-height: 30px; 35 | line-height: 20px; 36 | padding-left: 39px; 37 | } 38 | 39 | div.phpdebugbar-header, 40 | a.phpdebugbar-restore-btn, 41 | div.phpdebugbar-openhandler .phpdebugbar-openhandler-header { 42 | background: #f5f5f5 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIcAAACHCAYAAAA850oKAAAMfElEQVR42u2dC5RVZRXH7zwQGRTEqUEDxMdEQSggWAYjomlKGOAyylJBs5AhYVgIKYQ5hrgIfPVQwXgYQ4lppTApqxdiZGqAYy8cHIyKfCGi9lCPqO3N3VOX6d57vn3u9zrn7LvWfy3E4dzvfPs35+xvf3vvL5NhfoLxw4eC5oKaQW2gN0HvirxTANpBdpqHdsuY+MCFu4AaQNtl0mOtp0EzQFW6wJgEek4mNlFCe14MKosKxeGgn8hEJlpo32ouGLWgZ2TyUiG0cy0HjOdl0lL3mumn8iqRJ0Z6nyDVxeBYJ5OUaj2Q10mFv5wok5NKvQ36d85/T8wXx3hWJirRQgC2glaBrgKNJf+yghg4GnQdvV4OyYVjukxeoiE4FlSuuCDpDxqT+xcS+UwZBJy9Eplsf/VP4xAUgWOOGMAbCH4LuhM0Gx/t5AeUZVx94MtXimFSDkEROAaA3hKjGYVhOWgC6BgvIQgBZLEY0YgeBr3XIzt3B50KmgZaBtpM+TiohYUCYN1kW96IBjiCoJxiGOeB5oPuB+1UGO/VhS54gRhTuwZaAAF/sUeA6kG3g35Dr7Ko0dKz8n1JGWijGFSrlmqEoBL0IdD5oAWgtYpPA672gPrmG8BA0D4xqja9AzotAgi9QaNBXwY1gVooJ9TWuHE11TnfwG4So2rV30A9FID4OOgh0F5Pxr1EnFM7WqMAR71v4y400AvFoNp1UQgcnUBb4gAHOqebxKBa9RpGQkMAeX8JKw07cNBAB4lzql2b2vMnisz7RO/hoIF+UwyqXV9R8D+a4gAHhltfEINqFe5jnRQy74dSqam/cEhuqTFhclXXkHkfZjm2EQkOcU4dRU/hZ2Z5DQcNcjDF3cWoejVW4Rdzvddw0EBvFWNq127QESHzXuOqAhG/fLgiHD1AL4pBtevBsAQgCq2/4wKOzYy09c+LMY3ocoW5/7oLOPAPlyrCUUb5AmJQvXo9LDGIwuuPuYADYxndFAEZIs6pET2Rd5v8wLk/jsLwVuFALRbn1LkWKcz9Z13AEYT2aDiwVcNLYkw3yUHwMytsw4FqZjw9vijGdJMchIXOoFbbcKDOZmQ4PybGdJYcNCQw3OYzHxzb0DNWBGSYi/W3JAfZ6Y6QKfA/ZjBeL0vEkM6SgzC00GwbjldUK7XEOTWqXyskB73HVOOdjI6aC/jZyWJIY5qnMP+nm4g9ZUKqnwYznNPNYkhjyUEfVrDBAptw7C8EZjw9PiLOqdHkoEMUKuIesQkH6jMMQL4jhjSmOxTm/2jyF63B8RdQF0U40Dl6WQxpTOMUbDDBJhyoaxhPjyliRKPJQUcq2OAOm3Bg57o+4pzGJjmoCvQnW3Cg7mI8PU4WIxrVNAUbnAB6wxYcqDoGICvEiMb0hkrXIPiZqczrYq3MMqqV7sOFYysjpbDGo3YCSVRLWHIQ2eG+Itd4KgeG3vn+MXdQX2A8Pb4kRjSqxYrbG7tyYFhC3YGOUDEgd0CYUthdEY4KSn8TQ5pLDjpdwQ7HKsGgAQ7UjYzrf1SMaFS7VDoHRW1EFmVAmFL4AXFOncc8fgyamddfcAgH6gHGd/QEvSoGLUkYqV5NQcb+gY1OyCUOeDTje+RMF57QeVxKK4m+rloel3IDrYyUQtw1fFKMXjA9AqPKN4PODRy3xKYVzqcyGm5sJuNL6wSE/waxsCEwHpt1dth2vAUYDgqyPdExJ+Tx9sQhHXCgL1HDGMiqFMLwKrVSmEO/IJ0zjj8whoNB44PsqdT/0hE+j5xrkDLnFGNB94IaqISgwgMYutJ+CwJxBagRdEOx7kEZjcGYIYyBNiQMhj+DvovRY9AHPTk+o5KCX2fSCqexgB4yDcf+lELV5RUN/A8xhgHHfjvVrfb2BIZy6sA0i8oVcF/rr0H2LJ3GEO0yDQc3pfDUmICwjyr78BE8Dj35jCcfrG0Osm2x8RW2p8D48WSFa0PgmE9lmEbhQFKrGDf3PQ9hwMSmDTShZwQhXf8sw4CnKUyiV9guxj1tUHh6fIP6hBiDA9XIuNkjbfabKKC99Bi+kvaBDvIIhmqMN9ArbHuJPuEKBUB+YBoOpO8oxgTMtAwDnghxN7ZaCrItvMs9ggGr5z9Br7AWzaUeL1PrqI5A4C/F57A3HD2ZbjMJB+pupldt0jnF7KaVoEtAtRmPPhR8GgX6WpDt9Wr6lM4nyGH9NBZK4TZ+x0UExT9aTMKBGsmYpFEal9QYov82OseRchjMwlBBnQnwZOmfkX9j+zVarzBOPL3hHxnDlJYzJm5NxNQBbGCHx16eYyyvoTQg8Fy2aZSut9eT0P3xCuM+P2N4IJMZk9gLaQ25Hp5F8nOso8H2SJyVkUUYsOrsUtpef97T5fkflQrVDA/iRdWUQhrL7DxO1P30nsRa3EoPYaihnExM1N0Ro0DeUtdwoG5iTHQn8pYx3Dsw8PBo7yB7zMhY0C2g38c87H+eazj2qSTB+voh7x2DYdeDHg2S1YN1b9Gwg6VBPBojGCopGDYP9MtSq8Zi0j2o0gUcL1HyCLZm6OcpDGUUDJtBkdLXEg5DPs03DQc+bv9OS0uMQC7qEImb4osPgcEw0GU0zt2SmbbfdqN0wvE65QK0p7pdqRC7P8ERDO+jRN2VlMUtqYr561+qo8LxCqWUYXRvRMcNKsoyCoNjuo2sKEqQPZcipdvE8Mpae8DTXdcGFb27JysAcpIBGKro6bWIsrilN5mO9g45f/k0aDno4qgbVBQdDINjVqnb4rRhVUfXezhwfIpiAjPjB7VP9ASdG1S0/RsGyCkRUuBOJLDWF8qWFmnTNiNJTtQ07qshcFyl2oSOrtmjSBqcyIyWmXIIxyg8Pc5kXrNeDGZXJjOa5obAgUvgnszI5e/EaDGHg4w5sgMMC6jabRMFy3BFsYZ5zdPEaMmAowvoW7Q/sZM24PJlbg1lXvdeMVzM4KA4x2BKGG6mxByltHnm9xzTMYVe5CEctEcxhVLaS9mj+CTze68T43kGR84exZ1UwKSzUUklYxxdyW8RI7qCw/IexdQIwTYxoi04aI/irJw9CptZT5hv2o3p4zwiRjQIB+1RXEOdZlzvUVzPfHrI6ZSG4fBpQLgK6cMERNpYpgQOVBMTjp4pTe1LJRz4mhjGBGS2GDMdcKA2Rsjv2C4GTQccqHFMQM4Rg6YHjlZu+SMlAolhUwAH6nImHP0t9LgQODzRbk4hNgFysxg2HXCgFjLhOIyirWLgFMCBgbG+TEAuE+OmAw7U6gjZ6lvFwOmA490IgbERYuDIQqf+wTjBsTFCMtL3xdCsPirrqWXV4Tb7c+jSeCYcvaQAKrS6/heUzVfjqnmLLmGIvBMTkLkCwf/tXf0qyHY47Om67ZO5Ql/1tk3PCBT7W1Y1YKonZ/LidpPYLegwJiDjUwoEdlW6ImC0G487HKhFEe7zpykBYgvVIh+no94kjhPwJtavMO9zQIHCqiQIW15eHTAOgk4yHKh7ItzrLQkCAss5sKG+uWPDYj5BdSnbd8EOyXiq0vEZG5+Yw7E5YHYoDLKH9MUNiIXcmmKBI6uJEfZdtnh+T1hNeCPo5MBle87AzZkfOoVlkVXMex7u4X08S10JhgeO+7VSpeMl+Ie2BDw9GiNMgA8nY6P/gwcBnBI4Pk6MgoXYH+4+Km7bjn+5LgFw4P5Jrwj1Li5OxsZjQvD4jY8Fjk+qplcsNsRZnmcu1mYoYJKEpd2qCJMz3dLYcOKbQKO5e0OGoBhE9dDFjh+dgz84NEEbStycD+wz9qSh8WDzmjXUpaCzB0D0CbItyFX7qg1u/4dPJQSQTREmrU5zSuMPg+zJTVUeANGdlu4bmAXnrbkXaUhQ5HCCZef0LfLbLuJmyhsCojNtNN5D2wyl7XwH2eZuzyUEDtyeP9iwc/o2beT9L2vKLRBltOJZSg6v3tAAtXNKytNjboQJnqqYJJM/a8oNFLiZiMeL7TQaVCT61iUEDlza9o7w29dUIElmBitJxiwQ2JdtpqHs+uawyNiOhAByV4SJryD/60e0xD/KEyAOBU0KsqdYm2rF1Rb6igyy7SNfSAggIzMx/QTZY1TH0HLYdN9VjHfUqg6sNiFPkBbXUcgIrzY8mfLWwN7Zc20B93wdesUkwQepjwEU/UDXOtjnQvtWl0LyhTFf5u7xYbmZZ25rKHz/uKMd4Em6bqQLOWpxjaTe5gkQ2Hn5Aiw1dJTP2kr1Kl1N3eBQ8uTX0WMwDvkg6OEPcgQEroDwcMLVgfphAbpC+W1kJ7TXidyx/wdIVCWo/YcgUwAAAABJRU5ErkJggg==) no-repeat 5px 3px; 43 | } 44 | 45 | a.phpdebugbar-close-btn { 46 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuM4zml1AAAADDSURBVDhPxZCxCsIwFEUzuGdwCvQD7BIIcehUXDqVfGM/wsG/iG4ifkzMlRuSPLo4eeFBue8c6Iv6b4wxW557Hs0KnWa3seqDxTiOyVqbhmF4UND4Rofdruyce3rvE6bIRSo9GOI1McbLPM/vVm4l7MAQr0kpHaQsJTDE+6zrepym6SVFdNgR69M+hBTLzWCI10gJvydvBkO8ZlmWayvhJnkzGOI1+fBTCOHWPkT7YNiBId4HizxnCKy+r81uX/otSn0A7dioI/vYX+8AAAAASUVORK5CYII=) no-repeat 9px 6px; 47 | color : #555; 48 | } 49 | 50 | a.phpdebugbar-open-btn { 51 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAOCAYAAADJ7fe0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfdCgYULwwNKp3GAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4wLjOM5pdQAAAA1UlEQVQ4T2OgKpCUlOQH4vdA/B8Jv4dKEwYgDdLS0v8NDQ3/GxsbwzGIj2YoGEO1oQJkjcRgqDZUAJKwsrJ6/v//fwdiMFQbKgAZkpGR0QR0ajy60wlgRJhBXSGhpqb2CNnZhHBkZORcqBEMDFBX2BsYGGBVjAv39vZaQ41gYIC6Ygs2hbiwr6/vdqA+DqgR4CiW19bWxqoYF87Ly4uFaocAZWXlydgU4sJ2dna3ga4QgGqHAC0trY/YFOPCKSkpDVCtCAA01QaIsaYJHFgCqpVagIEBACGlF2c3r4ViAAAAAElFTkSuQmCC) no-repeat 8px 6px; 52 | } 53 | 54 | 55 | div.phpdebugbar-header, 56 | div.phpdebugbar-openhandler-header { 57 | background-size: 21px auto; 58 | background-position: 9px center; 59 | } 60 | 61 | a.phpdebugbar-restore-btn { 62 | background-size: 20px; 63 | width: 16px; 64 | border-right-color: #ccc; 65 | } 66 | 67 | div.phpdebugbar-header > div > * { 68 | font-size: 13px; 69 | } 70 | 71 | div.phpdebugbar-header .phpdebugbar-tab { 72 | padding: 5px 6px; 73 | } 74 | 75 | div.phpdebugbar .phpdebugbar-header select { 76 | padding: 1px 0; 77 | } 78 | 79 | dl.phpdebugbar-widgets-kvlist dt { 80 | width: 200px; 81 | min-height: 20px; 82 | padding: 7px 5px; 83 | line-height: 20px; 84 | } 85 | 86 | dl.phpdebugbar-widgets-kvlist dd { 87 | min-height: 20px; 88 | margin-left: 210px; 89 | padding: 7px 5px; 90 | line-height: 20px; 91 | } 92 | 93 | ul.phpdebugbar-widgets-timeline .phpdebugbar-widgets-measure { 94 | height: 25px; 95 | line-height: 25px; 96 | border: none; 97 | } 98 | 99 | ul.phpdebugbar-widgets-timeline li:nth-child(even) { 100 | background-color: #f9f9f9; 101 | } 102 | 103 | ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-value { 104 | height: 15px; 105 | background-color: #f4645f; 106 | } 107 | 108 | ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label, 109 | ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-collector { 110 | top: 0px; 111 | } 112 | 113 | div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter { 114 | background-color: #f4645f; 115 | } 116 | 117 | a.phpdebugbar-tab:hover, 118 | span.phpdebugbar-indicator:hover, 119 | a.phpdebugbar-indicator:hover, 120 | a.phpdebugbar-close-btn:hover, 121 | a.phpdebugbar-open-btn:hover { 122 | background-color: #ebebeb; 123 | transition: background-color .25s linear 0s, color .25s linear 0s; 124 | } 125 | 126 | a.phpdebugbar-tab.phpdebugbar-active { 127 | background: #f4645f; 128 | color: #fff; 129 | } 130 | 131 | a.phpdebugbar-tab.phpdebugbar-active span.phpdebugbar-badge { 132 | background-color: white; 133 | color: #f4645f; 134 | } 135 | 136 | a.phpdebugbar-tab span.phpdebugbar-badge { 137 | vertical-align: 0px; 138 | padding: 2px 6px; 139 | background: #f4645f; 140 | font-size: 12px; 141 | color: #fff; 142 | border-radius: 10px; 143 | } 144 | 145 | div.phpdebugbar-openhandler .phpdebugbar-openhandler-header { 146 | background-size: 20px; 147 | } 148 | 149 | div.phpdebugbar-openhandler a { 150 | color: #555; 151 | } 152 | 153 | div.phpdebugbar-openhandler table { 154 | table-layout: fixed; 155 | } 156 | 157 | div.phpdebugbar-openhandler table td, 158 | div.phpdebugbar-openhandler table th { 159 | text-align: left; 160 | } 161 | 162 | div.phpdebugbar-openhandler table td a { 163 | display: block; 164 | white-space: nowrap; 165 | overflow: hidden; 166 | text-overflow: ellipsis; 167 | } 168 | 169 | .phpdebugbar-indicator span.phpdebugbar-tooltip { 170 | top: -36px; 171 | border: none; 172 | border-radius: 5px; 173 | background: #f5f5f5; 174 | font-size: 12px; 175 | } 176 | 177 | div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter { 178 | margin: 0; 179 | padding: 5px 8px; 180 | border-radius: 0; 181 | font-size: 12px; 182 | transition: background-color .25s linear 0s, color .25s linear 0s; 183 | } 184 | 185 | div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter:hover { 186 | background-color: #ad4844; 187 | color: #fff; 188 | } 189 | 190 | .phpdebugbar-widgets-toolbar > .fa { 191 | width: 25px; 192 | font-size: 15px; 193 | color: #555; 194 | text-align: center; 195 | } 196 | 197 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item { 198 | padding: 5px 10px; 199 | border: none; 200 | font-family: inherit; 201 | overflow: visible; 202 | display: flex; 203 | flex-wrap: wrap; 204 | } 205 | 206 | .phpdebugbar-widgets-sql.phpdebugbar-widgets-name { 207 | font-weight: bold; 208 | } 209 | 210 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-sql { 211 | flex: 1; 212 | margin-right: 5px; 213 | } 214 | 215 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-duration { 216 | /*flex: 0 0 auto;*/ 217 | margin-left: auto; 218 | margin-right: 5px; 219 | } 220 | 221 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-database { 222 | /*flex: 0 0 auto;*/ 223 | margin-left: auto; 224 | } 225 | 226 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id { 227 | /*flex: 0 0 auto;*/ 228 | margin-left: auto; 229 | margin-right: 5px; 230 | } 231 | 232 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-params { 233 | background-color: rgba(255, 255, 255, .5); 234 | flex: 1 1 auto; 235 | margin: 10px 100% 10px 0; 236 | max-width: 100%; 237 | } 238 | 239 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:nth-child(even) { 240 | background-color: #f9f9f9; 241 | } 242 | 243 | div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error:before { 244 | font-size: 12px; 245 | color: #e74c3c; 246 | } 247 | 248 | div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-warning:before { 249 | font-size: 12px; 250 | color: #f1c40f; 251 | } 252 | 253 | div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error { 254 | color: #e74c3c; 255 | } 256 | 257 | .phpdebugbar-widgets-value.phpdebugbar-widgets-warning { 258 | color: #f1c40f; 259 | } 260 | 261 | div.phpdebugbar-widgets-sqlqueries { 262 | line-height: 20px; 263 | } 264 | 265 | div.phpdebugbar-widgets-sqlqueries .phpdebugbar-widgets-status { 266 | background: none !important; 267 | font-family: inherit !important; 268 | font-weight: 400 !important; 269 | } 270 | 271 | div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params th, 272 | div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td { 273 | padding: 5px 10px; 274 | } 275 | 276 | div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-name { 277 | text-align: right; 278 | vertical-align: top; 279 | white-space: nowrap; 280 | } 281 | 282 | div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-value { 283 | text-align: left; 284 | } 285 | 286 | ul.phpdebugbar-widgets-list ul.phpdebugbar-widgets-table-list { 287 | text-align: left; 288 | } 289 | 290 | ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-table-list-item { 291 | /*padding: 5px 10px;*/ 292 | } 293 | 294 | .phpdebugbar-text-muted { 295 | color: #888; 296 | } 297 | -------------------------------------------------------------------------------- /src/DataCollector/QueryCollector.php: -------------------------------------------------------------------------------- 1 | timeCollector = $timeCollector; 29 | } 30 | 31 | /** 32 | * Renders the SQL of traced statements with params embedded 33 | * 34 | * @param boolean $enabled 35 | * @param string $quotationChar NOT USED 36 | */ 37 | public function setRenderSqlWithParams($enabled = true, $quotationChar = "'") 38 | { 39 | $this->renderSqlWithParams = $enabled; 40 | } 41 | 42 | /** 43 | * Show or hide the hints in the parameters 44 | * 45 | * @param boolean $enabled 46 | */ 47 | public function setShowHints($enabled = true) 48 | { 49 | $this->showHints = $enabled; 50 | } 51 | 52 | /** 53 | * Enable/disable finding the source 54 | * 55 | * @param bool $value 56 | * @param array $middleware 57 | */ 58 | public function setFindSource($value, array $middleware) 59 | { 60 | $this->findSource = (bool) $value; 61 | $this->middleware = $middleware; 62 | } 63 | 64 | /** 65 | * Enable/disable the EXPLAIN queries 66 | * 67 | * @param bool $enabled 68 | * @param array|null $types Array of types to explain queries (select/insert/update/delete) 69 | */ 70 | public function setExplainSource($enabled, $types) 71 | { 72 | $this->explainQuery = $enabled; 73 | if($types){ 74 | $this->explainTypes = $types; 75 | } 76 | } 77 | 78 | /** 79 | * 80 | * @param string $query 81 | * @param array $bindings 82 | * @param float $time 83 | * @param \Illuminate\Database\Connection $connection 84 | */ 85 | public function addQuery($query, $bindings, $time, $connection) 86 | { 87 | $explainResults = []; 88 | $time = $time / 1000; 89 | $endTime = microtime(true); 90 | $startTime = $endTime - $time; 91 | $hints = $this->performQueryAnalysis($query); 92 | 93 | $pdo = $connection->getPdo(); 94 | $bindings = $connection->prepareBindings($bindings); 95 | 96 | // Run EXPLAIN on this query (if needed) 97 | if ($this->explainQuery && preg_match('/^('.implode($this->explainTypes).') /i', $query)) { 98 | $statement = $pdo->prepare('EXPLAIN ' . $query); 99 | $statement->execute($bindings); 100 | $explainResults = $statement->fetchAll(\PDO::FETCH_CLASS); 101 | } 102 | 103 | $bindings = $this->getDataFormatter()->checkBindings($bindings); 104 | if (!empty($bindings) && $this->renderSqlWithParams) { 105 | foreach ($bindings as $key => $binding) { 106 | // This regex matches placeholders only, not the question marks, 107 | // nested in quotes, while we iterate through the bindings 108 | // and substitute placeholders by suitable values. 109 | $regex = is_numeric($key) 110 | ? "/\?(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/" 111 | : "/:{$key}(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/"; 112 | $query = preg_replace($regex, $pdo->quote($binding), $query, 1); 113 | } 114 | } 115 | 116 | $source = []; 117 | 118 | if ($this->findSource) { 119 | try { 120 | $source = $this->findSource(); 121 | } catch (\Exception $e) { 122 | } 123 | } 124 | 125 | $this->queries[] = [ 126 | 'query' => $query, 127 | 'type' => 'query', 128 | 'bindings' => $this->getDataFormatter()->escapeBindings($bindings), 129 | 'time' => $time, 130 | 'source' => $source, 131 | 'explain' => $explainResults, 132 | 'connection' => $connection->getDatabaseName(), 133 | 'hints' => $this->showHints ? $hints : null, 134 | ]; 135 | 136 | if ($this->timeCollector !== null) { 137 | $this->timeCollector->addMeasure($query, $startTime, $endTime); 138 | } 139 | } 140 | 141 | /** 142 | * Explainer::performQueryAnalysis() 143 | * 144 | * Perform simple regex analysis on the code 145 | * 146 | * @package xplain (https://github.com/rap2hpoutre/mysql-xplain-xplain) 147 | * @author e-doceo 148 | * @copyright 2014 149 | * @version $Id$ 150 | * @access public 151 | * @param string $query 152 | * @return string 153 | */ 154 | protected function performQueryAnalysis($query) 155 | { 156 | $hints = []; 157 | if (preg_match('/^\\s*SELECT\\s*`?[a-zA-Z0-9]*`?\\.?\\*/i', $query)) { 158 | $hints[] = 'Use SELECT * only if you need all columns from table'; 159 | } 160 | if (preg_match('/ORDER BY RAND()/i', $query)) { 161 | $hints[] = 'ORDER BY RAND() is slow, try to avoid if you can. 162 | You can
read this 163 | or this'; 164 | } 165 | if (strpos($query, '!=') !== false) { 166 | $hints[] = 'The != operator is not standard. Use the <> operator to test for inequality instead.'; 167 | } 168 | if (stripos($query, 'WHERE') === false && preg_match('/^(SELECT) /i', $query)) { 169 | $hints[] = 'The SELECT statement has no WHERE clause and could examine many more rows than intended'; 170 | } 171 | if (preg_match('/LIMIT\\s/i', $query) && stripos($query, 'ORDER BY') === false) { 172 | $hints[] = 'LIMIT without ORDER BY causes non-deterministic results, depending on the query execution plan'; 173 | } 174 | if (preg_match('/LIKE\\s[\'"](%.*?)[\'"]/i', $query, $matches)) { 175 | $hints[] = 'An argument has a leading wildcard character: ' . $matches[1]. '. 176 | The predicate with this argument is not sargable and cannot use an index if one exists.'; 177 | } 178 | return $hints; 179 | } 180 | 181 | /** 182 | * Use a backtrace to search for the origins of the query. 183 | * 184 | * @return array 185 | */ 186 | protected function findSource() 187 | { 188 | $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); 189 | 190 | $sources = []; 191 | 192 | foreach ($stack as $index => $trace) { 193 | $sources[] = $this->parseTrace($index, $trace); 194 | } 195 | 196 | return array_filter($sources); 197 | } 198 | 199 | /** 200 | * Parse a trace element from the backtrace stack. 201 | * 202 | * @param int $index 203 | * @param array $trace 204 | * @return object|bool 205 | */ 206 | protected function parseTrace($index, array $trace) 207 | { 208 | $frame = (object) [ 209 | 'index' => $index, 210 | 'namespace' => null, 211 | 'name' => null, 212 | 'line' => isset($trace['line']) ? $trace['line'] : '?', 213 | ]; 214 | 215 | if (isset($trace['function']) && $trace['function'] == 'substituteBindings') { 216 | $frame->name = 'Route binding'; 217 | 218 | return $frame; 219 | } 220 | 221 | if (isset($trace['class']) && isset($trace['file']) && strpos( 222 | $trace['file'], 223 | DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'laravel' . DIRECTORY_SEPARATOR . 'framework' 224 | ) === false && strpos( 225 | $trace['file'], 226 | DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'barryvdh' . DIRECTORY_SEPARATOR . 'laravel-debugbar' 227 | ) === false 228 | ) { 229 | $file = $trace['file']; 230 | 231 | if (isset($trace['object']) && is_a($trace['object'], 'Twig_Template')) { 232 | list($file, $frame->line) = $this->getTwigInfo($trace); 233 | } elseif (strpos($file, storage_path()) !== false) { 234 | $hash = pathinfo($file, PATHINFO_FILENAME); 235 | 236 | if (! $frame->name = $this->findViewFromHash($hash)) { 237 | $frame->name = $hash; 238 | } 239 | 240 | $frame->namespace = 'view'; 241 | 242 | return $frame; 243 | } elseif (strpos($file, 'Middleware') !== false) { 244 | $frame->name = $this->findMiddlewareFromFile($file); 245 | 246 | if ($frame->name) { 247 | $frame->namespace = 'middleware'; 248 | } else { 249 | $frame->name = $this->normalizeFilename($file); 250 | } 251 | 252 | return $frame; 253 | } 254 | 255 | $frame->name = $this->normalizeFilename($file); 256 | 257 | return $frame; 258 | } 259 | 260 | 261 | return false; 262 | } 263 | 264 | /** 265 | * Find the middleware alias from the file. 266 | * 267 | * @param string $file 268 | * @return string|null 269 | */ 270 | protected function findMiddlewareFromFile($file) 271 | { 272 | $filename = pathinfo($file, PATHINFO_FILENAME); 273 | 274 | foreach ($this->middleware as $alias => $class) { 275 | if (strpos($class, $filename) !== false) { 276 | return $alias; 277 | } 278 | } 279 | } 280 | 281 | /** 282 | * Find the template name from the hash. 283 | * 284 | * @param string $hash 285 | * @return null|string 286 | */ 287 | protected function findViewFromHash($hash) 288 | { 289 | $finder = app('view')->getFinder(); 290 | 291 | if (isset($this->reflection['viewfinderViews'])) { 292 | $property = $this->reflection['viewfinderViews']; 293 | } else { 294 | $reflection = new \ReflectionClass($finder); 295 | $property = $reflection->getProperty('views'); 296 | $property->setAccessible(true); 297 | $this->reflection['viewfinderViews'] = $property; 298 | } 299 | 300 | foreach ($property->getValue($finder) as $name => $path){ 301 | if (sha1($path) == $hash || md5($path) == $hash) { 302 | return $name; 303 | } 304 | } 305 | } 306 | 307 | /** 308 | * Get the filename/line from a Twig template trace 309 | * 310 | * @param array $trace 311 | * @return array The file and line 312 | */ 313 | protected function getTwigInfo($trace) 314 | { 315 | $file = $trace['object']->getTemplateName(); 316 | 317 | if (isset($trace['line'])) { 318 | foreach ($trace['object']->getDebugInfo() as $codeLine => $templateLine) { 319 | if ($codeLine <= $trace['line']) { 320 | return [$file, $templateLine]; 321 | } 322 | } 323 | } 324 | 325 | return [$file, -1]; 326 | } 327 | 328 | /** 329 | * Shorten the path by removing the relative links and base dir 330 | * 331 | * @param string $path 332 | * @return string 333 | */ 334 | protected function normalizeFilename($path) 335 | { 336 | if (file_exists($path)) { 337 | $path = realpath($path); 338 | } 339 | return str_replace(base_path(), '', $path); 340 | } 341 | 342 | /** 343 | * Collect a database transaction event. 344 | * @param string $event 345 | * @param \Illuminate\Database\Connection $connection 346 | * @return array 347 | */ 348 | public function collectTransactionEvent($event, $connection) 349 | { 350 | $source = []; 351 | 352 | if ($this->findSource) { 353 | try { 354 | $source = $this->findSource(); 355 | } catch (\Exception $e) { 356 | } 357 | } 358 | 359 | $this->queries[] = [ 360 | 'query' => $event, 361 | 'type' => 'transaction', 362 | 'bindings' => [], 363 | 'time' => 0, 364 | 'source' => $source, 365 | 'explain' => [], 366 | 'connection' => $connection->getDatabaseName(), 367 | 'hints' => null, 368 | ]; 369 | } 370 | 371 | /** 372 | * Reset the queries. 373 | */ 374 | public function reset() 375 | { 376 | $this->queries = []; 377 | } 378 | 379 | /** 380 | * {@inheritDoc} 381 | */ 382 | public function collect() 383 | { 384 | $totalTime = 0; 385 | $queries = $this->queries; 386 | 387 | $statements = []; 388 | foreach ($queries as $query) { 389 | $totalTime += $query['time']; 390 | 391 | $statements[] = [ 392 | 'sql' => $this->getDataFormatter()->formatSql($query['query']), 393 | 'type' => $query['type'], 394 | 'params' => [], 395 | 'bindings' => $query['bindings'], 396 | 'hints' => $query['hints'], 397 | 'backtrace' => array_values($query['source']), 398 | 'duration' => $query['time'], 399 | 'duration_str' => ($query['type'] == 'transaction') ? '' : $this->formatDuration($query['time']), 400 | 'stmt_id' => $this->getDataFormatter()->formatSource(reset($query['source'])), 401 | 'connection' => $query['connection'], 402 | ]; 403 | 404 | //Add the results from the explain as new rows 405 | foreach($query['explain'] as $explain){ 406 | $statements[] = [ 407 | 'sql' => ' - EXPLAIN #' . $explain->id . ': `' . $explain->table . '` (' . $explain->select_type . ')', 408 | 'type' => 'explain', 409 | 'params' => $explain, 410 | 'row_count' => $explain->rows, 411 | 'stmt_id' => $explain->id, 412 | ]; 413 | } 414 | } 415 | 416 | $nb_statements = array_filter($queries, function ($query) { 417 | return $query['type'] == 'query'; 418 | }); 419 | 420 | $data = [ 421 | 'nb_statements' => count($nb_statements), 422 | 'nb_failed_statements' => 0, 423 | 'accumulated_duration' => $totalTime, 424 | 'accumulated_duration_str' => $this->formatDuration($totalTime), 425 | 'statements' => $statements 426 | ]; 427 | return $data; 428 | } 429 | 430 | /** 431 | * {@inheritDoc} 432 | */ 433 | public function getName() 434 | { 435 | return 'queries'; 436 | } 437 | 438 | /** 439 | * {@inheritDoc} 440 | */ 441 | public function getWidgets() 442 | { 443 | return [ 444 | "queries" => [ 445 | "icon" => "database", 446 | "widget" => "PhpDebugBar.Widgets.LaravelSQLQueriesWidget", 447 | "map" => "queries", 448 | "default" => "[]" 449 | ], 450 | "queries:badge" => [ 451 | "map" => "queries.nb_statements", 452 | "default" => 0 453 | ] 454 | ]; 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /src/LaravelDebugbar.php: -------------------------------------------------------------------------------- 1 | app = $app; 97 | $this->version = $app->version(); 98 | $this->is_lumen = str_contains($this->version, 'Lumen'); 99 | } 100 | 101 | /** 102 | * Enable the Debugbar and boot, if not already booted. 103 | */ 104 | public function enable() 105 | { 106 | $this->enabled = true; 107 | 108 | if (!$this->booted) { 109 | $this->boot(); 110 | } 111 | } 112 | 113 | /** 114 | * Boot the debugbar (add collectors, renderer and listener) 115 | */ 116 | public function boot() 117 | { 118 | if ($this->booted) { 119 | return; 120 | } 121 | 122 | /** @var \Barryvdh\Debugbar\LaravelDebugbar $debugbar */ 123 | $debugbar = $this; 124 | 125 | /** @var Application $app */ 126 | $app = $this->app; 127 | 128 | // Set custom error handler 129 | if ($app['config']->get('debugbar.error_handler' , false)) { 130 | set_error_handler([$this, 'handleError']); 131 | } 132 | 133 | $this->selectStorage($debugbar); 134 | 135 | if ($this->shouldCollect('phpinfo', true)) { 136 | $this->addCollector(new PhpInfoCollector()); 137 | } 138 | 139 | if ($this->shouldCollect('messages', true)) { 140 | $this->addCollector(new MessagesCollector()); 141 | } 142 | 143 | if ($this->shouldCollect('time', true)) { 144 | $this->addCollector(new TimeDataCollector()); 145 | 146 | if ( ! $this->isLumen()) { 147 | $this->app->booted( 148 | function () use ($debugbar) { 149 | $startTime = $this->app['request']->server('REQUEST_TIME_FLOAT'); 150 | if ($startTime) { 151 | $debugbar['time']->addMeasure('Booting', $startTime, microtime(true)); 152 | } 153 | } 154 | ); 155 | } 156 | 157 | $debugbar->startMeasure('application', 'Application'); 158 | } 159 | 160 | if ($this->shouldCollect('memory', true)) { 161 | $this->addCollector(new MemoryCollector()); 162 | } 163 | 164 | if ($this->shouldCollect('exceptions', true)) { 165 | try { 166 | $exceptionCollector = new ExceptionsCollector(); 167 | $exceptionCollector->setChainExceptions( 168 | $this->app['config']->get('debugbar.options.exceptions.chain', true) 169 | ); 170 | $this->addCollector($exceptionCollector); 171 | } catch (\Exception $e) { 172 | } 173 | } 174 | 175 | if ($this->shouldCollect('laravel', false)) { 176 | $this->addCollector(new LaravelCollector($this->app)); 177 | } 178 | 179 | if ($this->shouldCollect('default_request', false)) { 180 | $this->addCollector(new RequestDataCollector()); 181 | } 182 | 183 | if ($this->shouldCollect('events', false) && isset($this->app['events'])) { 184 | try { 185 | $startTime = $this->app['request']->server('REQUEST_TIME_FLOAT'); 186 | $eventCollector = new EventCollector($startTime); 187 | $this->addCollector($eventCollector); 188 | $this->app['events']->subscribe($eventCollector); 189 | 190 | } catch (\Exception $e) { 191 | $this->addThrowable( 192 | new Exception( 193 | 'Cannot add EventCollector to Laravel Debugbar: ' . $e->getMessage(), 194 | $e->getCode(), 195 | $e 196 | ) 197 | ); 198 | } 199 | } 200 | 201 | if ($this->shouldCollect('views', true) && isset($this->app['events'])) { 202 | try { 203 | $collectData = $this->app['config']->get('debugbar.options.views.data', true); 204 | $this->addCollector(new ViewCollector($collectData)); 205 | $this->app['events']->listen( 206 | 'composing:*', 207 | function ($view, $data = []) use ($debugbar) { 208 | if ($data) { 209 | $view = $data[0]; // For Laravel >= 5.4 210 | } 211 | $debugbar['views']->addView($view); 212 | } 213 | ); 214 | } catch (\Exception $e) { 215 | $this->addThrowable( 216 | new Exception( 217 | 'Cannot add ViewCollector to Laravel Debugbar: ' . $e->getMessage(), $e->getCode(), $e 218 | ) 219 | ); 220 | } 221 | } 222 | 223 | if (!$this->isLumen() && $this->shouldCollect('route')) { 224 | try { 225 | $this->addCollector($this->app->make('Barryvdh\Debugbar\DataCollector\IlluminateRouteCollector')); 226 | } catch (\Exception $e) { 227 | $this->addThrowable( 228 | new Exception( 229 | 'Cannot add RouteCollector to Laravel Debugbar: ' . $e->getMessage(), 230 | $e->getCode(), 231 | $e 232 | ) 233 | ); 234 | } 235 | } 236 | 237 | if (!$this->isLumen() && $this->shouldCollect('log', true)) { 238 | try { 239 | if ($this->hasCollector('messages')) { 240 | $logger = new MessagesCollector('log'); 241 | $this['messages']->aggregate($logger); 242 | $this->app['log']->listen( 243 | function ($level, $message = null, $context = null) use ($logger) { 244 | // Laravel 5.4 changed how the global log listeners are called. We must account for 245 | // the first argument being an "event object", where arguments are passed 246 | // via object properties, instead of individual arguments. 247 | if ($level instanceof \Illuminate\Log\Events\MessageLogged) { 248 | $message = $level->message; 249 | $context = $level->context; 250 | $level = $level->level; 251 | } 252 | 253 | try { 254 | $logMessage = (string) $message; 255 | if (mb_check_encoding($logMessage, 'UTF-8')) { 256 | $logMessage .= (!empty($context) ? ' ' . json_encode($context) : ''); 257 | } else { 258 | $logMessage = "[INVALID UTF-8 DATA]"; 259 | } 260 | } catch (\Exception $e) { 261 | $logMessage = "[Exception: " . $e->getMessage() . "]"; 262 | } 263 | $logger->addMessage( 264 | '[' . date('H:i:s') . '] ' . "LOG.$level: " . $logMessage, 265 | $level, 266 | false 267 | ); 268 | } 269 | ); 270 | } else { 271 | $this->addCollector(new MonologCollector($this->app['log']->getMonolog())); 272 | } 273 | } catch (\Exception $e) { 274 | $this->addThrowable( 275 | new Exception( 276 | 'Cannot add LogsCollector to Laravel Debugbar: ' . $e->getMessage(), $e->getCode(), $e 277 | ) 278 | ); 279 | } 280 | } 281 | 282 | if ($this->shouldCollect('db', true) && isset($this->app['db'])) { 283 | $db = $this->app['db']; 284 | if ($debugbar->hasCollector('time') && $this->app['config']->get( 285 | 'debugbar.options.db.timeline', 286 | false 287 | ) 288 | ) { 289 | $timeCollector = $debugbar->getCollector('time'); 290 | } else { 291 | $timeCollector = null; 292 | } 293 | $queryCollector = new QueryCollector($timeCollector); 294 | 295 | $queryCollector->setDataFormatter(new QueryFormatter()); 296 | 297 | if ($this->app['config']->get('debugbar.options.db.with_params')) { 298 | $queryCollector->setRenderSqlWithParams(true); 299 | } 300 | 301 | if ($this->app['config']->get('debugbar.options.db.backtrace')) { 302 | $middleware = ! $this->is_lumen ? $this->app['router']->getMiddleware() : []; 303 | $queryCollector->setFindSource(true, $middleware); 304 | } 305 | 306 | if ($this->app['config']->get('debugbar.options.db.explain.enabled')) { 307 | $types = $this->app['config']->get('debugbar.options.db.explain.types'); 308 | $queryCollector->setExplainSource(true, $types); 309 | } 310 | 311 | if ($this->app['config']->get('debugbar.options.db.hints', true)) { 312 | $queryCollector->setShowHints(true); 313 | } 314 | 315 | $this->addCollector($queryCollector); 316 | 317 | try { 318 | $db->listen( 319 | function ($query, $bindings = null, $time = null, $connectionName = null) use ($db, $queryCollector) { 320 | // Laravel 5.2 changed the way some core events worked. We must account for 321 | // the first argument being an "event object", where arguments are passed 322 | // via object properties, instead of individual arguments. 323 | if ( $query instanceof \Illuminate\Database\Events\QueryExecuted ) { 324 | $bindings = $query->bindings; 325 | $time = $query->time; 326 | $connection = $query->connection; 327 | 328 | $query = $query->sql; 329 | } else { 330 | $connection = $db->connection($connectionName); 331 | } 332 | 333 | $queryCollector->addQuery((string) $query, $bindings, $time, $connection); 334 | } 335 | ); 336 | } catch (\Exception $e) { 337 | $this->addThrowable( 338 | new Exception( 339 | 'Cannot add listen to Queries for Laravel Debugbar: ' . $e->getMessage(), 340 | $e->getCode(), 341 | $e 342 | ) 343 | ); 344 | } 345 | 346 | try { 347 | $db->getEventDispatcher()->listen([ 348 | \Illuminate\Database\Events\TransactionBeginning::class, 349 | 'connection.*.beganTransaction', 350 | ], function ($transaction) use ($queryCollector) { 351 | 352 | // Laravel 5.2 changed the way some core events worked. We must account for 353 | // the first argument being an "event object", where arguments are passed 354 | // via object properties, instead of individual arguments. 355 | if($transaction instanceof \Illuminate\Database\Events\TransactionBeginning) { 356 | $connection = $transaction->connection; 357 | } else { 358 | $connection = $transaction; 359 | } 360 | 361 | $queryCollector->collectTransactionEvent('Begin Transaction', $connection); 362 | }); 363 | 364 | $db->getEventDispatcher()->listen([ 365 | \Illuminate\Database\Events\TransactionCommitted::class, 366 | 'connection.*.committed', 367 | ], function ($transaction) use ($queryCollector) { 368 | 369 | if($transaction instanceof \Illuminate\Database\Events\TransactionCommitted) { 370 | $connection = $transaction->connection; 371 | } else { 372 | $connection = $transaction; 373 | } 374 | 375 | $queryCollector->collectTransactionEvent('Commit Transaction', $connection); 376 | }); 377 | 378 | $db->getEventDispatcher()->listen([ 379 | \Illuminate\Database\Events\TransactionRolledBack::class, 380 | 'connection.*.rollingBack', 381 | ], function ($transaction) use ($queryCollector) { 382 | 383 | if($transaction instanceof \Illuminate\Database\Events\TransactionRolledBack) { 384 | $connection = $transaction->connection; 385 | } else { 386 | $connection = $transaction; 387 | } 388 | 389 | $queryCollector->collectTransactionEvent('Rollback Transaction', $connection); 390 | }); 391 | } catch (\Exception $e) { 392 | $this->addThrowable( 393 | new Exception( 394 | 'Cannot add listen transactions to Queries for Laravel Debugbar: ' . $e->getMessage(), 395 | $e->getCode(), 396 | $e 397 | ) 398 | ); 399 | } 400 | } 401 | 402 | if ($this->shouldCollect('mail', true) && class_exists('Illuminate\Mail\MailServiceProvider')) { 403 | try { 404 | $mailer = $this->app['mailer']->getSwiftMailer(); 405 | $this->addCollector(new SwiftMailCollector($mailer)); 406 | if ($this->app['config']->get('debugbar.options.mail.full_log') && $this->hasCollector( 407 | 'messages' 408 | ) 409 | ) { 410 | $this['messages']->aggregate(new SwiftLogCollector($mailer)); 411 | } 412 | } catch (\Exception $e) { 413 | $this->addThrowable( 414 | new Exception( 415 | 'Cannot add MailCollector to Laravel Debugbar: ' . $e->getMessage(), $e->getCode(), $e 416 | ) 417 | ); 418 | } 419 | } 420 | 421 | if ($this->shouldCollect('logs', false)) { 422 | try { 423 | $file = $this->app['config']->get('debugbar.options.logs.file'); 424 | $this->addCollector(new LogsCollector($file)); 425 | } catch (\Exception $e) { 426 | $this->addThrowable( 427 | new Exception( 428 | 'Cannot add LogsCollector to Laravel Debugbar: ' . $e->getMessage(), $e->getCode(), $e 429 | ) 430 | ); 431 | } 432 | } 433 | if ($this->shouldCollect('files', false)) { 434 | $this->addCollector(new FilesCollector($app)); 435 | } 436 | 437 | if ($this->shouldCollect('auth', false)) { 438 | try { 439 | if($this->checkVersion('5.2')) { 440 | // fix for compatibility with Laravel 5.2.* 441 | $guards = array_keys($this->app['config']->get('auth.guards')); 442 | $authCollector = new MultiAuthCollector($app['auth'], $guards); 443 | } else { 444 | $authCollector = new AuthCollector($app['auth']); 445 | } 446 | 447 | $authCollector->setShowName( 448 | $this->app['config']->get('debugbar.options.auth.show_name') 449 | ); 450 | $this->addCollector($authCollector); 451 | } catch (\Exception $e) { 452 | $this->addThrowable( 453 | new Exception( 454 | 'Cannot add AuthCollector to Laravel Debugbar: ' . $e->getMessage(), $e->getCode(), $e 455 | ) 456 | ); 457 | } 458 | } 459 | 460 | if ($this->shouldCollect('gate', false)) { 461 | try { 462 | $gateCollector = $this->app->make('Barryvdh\Debugbar\DataCollector\GateCollector'); 463 | $this->addCollector($gateCollector); 464 | } catch (\Exception $e){ 465 | // No Gate collector 466 | } 467 | } 468 | 469 | $renderer = $this->getJavascriptRenderer(); 470 | $renderer->setIncludeVendors($this->app['config']->get('debugbar.include_vendors', true)); 471 | $renderer->setBindAjaxHandlerToXHR($app['config']->get('debugbar.capture_ajax', true)); 472 | 473 | $this->booted = true; 474 | } 475 | 476 | public function shouldCollect($name, $default = false) 477 | { 478 | return $this->app['config']->get('debugbar.collectors.' . $name, $default); 479 | } 480 | 481 | /** 482 | * Handle silenced errors 483 | * 484 | * @param $level 485 | * @param $message 486 | * @param string $file 487 | * @param int $line 488 | * @param array $context 489 | * @throws \ErrorException 490 | */ 491 | public function handleError($level, $message, $file = '', $line = 0, $context = []) 492 | { 493 | if (error_reporting() & $level) { 494 | throw new \ErrorException($message, 0, $level, $file, $line); 495 | } else { 496 | $this->addMessage($message, 'deprecation'); 497 | } 498 | } 499 | 500 | /** 501 | * Starts a measure 502 | * 503 | * @param string $name Internal name, used to stop the measure 504 | * @param string $label Public name 505 | */ 506 | public function startMeasure($name, $label = null) 507 | { 508 | if ($this->hasCollector('time')) { 509 | /** @var \DebugBar\DataCollector\TimeDataCollector $collector */ 510 | $collector = $this->getCollector('time'); 511 | $collector->startMeasure($name, $label); 512 | } 513 | } 514 | 515 | /** 516 | * Stops a measure 517 | * 518 | * @param string $name 519 | */ 520 | public function stopMeasure($name) 521 | { 522 | if ($this->hasCollector('time')) { 523 | /** @var \DebugBar\DataCollector\TimeDataCollector $collector */ 524 | $collector = $this->getCollector('time'); 525 | try { 526 | $collector->stopMeasure($name); 527 | } catch (\Exception $e) { 528 | // $this->addThrowable($e); 529 | } 530 | } 531 | } 532 | 533 | /** 534 | * Adds an exception to be profiled in the debug bar 535 | * 536 | * @param Exception $e 537 | * @deprecated in favor of addThrowable 538 | */ 539 | public function addException(Exception $e) 540 | { 541 | return $this->addThrowable($e); 542 | } 543 | 544 | /** 545 | * Adds an exception to be profiled in the debug bar 546 | * 547 | * @param Exception $e 548 | */ 549 | public function addThrowable($e) 550 | { 551 | if ($this->hasCollector('exceptions')) { 552 | /** @var \DebugBar\DataCollector\ExceptionsCollector $collector */ 553 | $collector = $this->getCollector('exceptions'); 554 | $collector->addThrowable($e); 555 | } 556 | } 557 | 558 | /** 559 | * Returns a JavascriptRenderer for this instance 560 | * 561 | * @param string $baseUrl 562 | * @param string $basePathng 563 | * @return JavascriptRenderer 564 | */ 565 | public function getJavascriptRenderer($baseUrl = null, $basePath = null) 566 | { 567 | if ($this->jsRenderer === null) { 568 | $this->jsRenderer = new JavascriptRenderer($this, $baseUrl, $basePath); 569 | } 570 | return $this->jsRenderer; 571 | } 572 | 573 | /** 574 | * Modify the response and inject the debugbar (or data in headers) 575 | * 576 | * @param \Symfony\Component\HttpFoundation\Request $request 577 | * @param \Symfony\Component\HttpFoundation\Response $response 578 | * @return \Symfony\Component\HttpFoundation\Response 579 | */ 580 | public function modifyResponse(Request $request, Response $response) 581 | { 582 | $app = $this->app; 583 | if ($app->runningInConsole() || !$this->isEnabled() || $this->isDebugbarRequest()) { 584 | return $response; 585 | } 586 | 587 | // Show the Http Response Exception in the Debugbar, when available 588 | if (isset($response->exception)) { 589 | $this->addThrowable($response->exception); 590 | } 591 | 592 | if ($this->shouldCollect('config', false)) { 593 | try { 594 | $configCollector = new ConfigCollector(); 595 | $configCollector->setData($app['config']->all()); 596 | $this->addCollector($configCollector); 597 | } catch (\Exception $e) { 598 | $this->addThrowable( 599 | new Exception( 600 | 'Cannot add ConfigCollector to Laravel Debugbar: ' . $e->getMessage(), 601 | $e->getCode(), 602 | $e 603 | ) 604 | ); 605 | } 606 | } 607 | 608 | if ($this->app->bound(SessionManager::class)){ 609 | 610 | /** @var \Illuminate\Session\SessionManager $sessionManager */ 611 | $sessionManager = $app->make(SessionManager::class); 612 | $httpDriver = new SymfonyHttpDriver($sessionManager, $response); 613 | $this->setHttpDriver($httpDriver); 614 | 615 | if ($this->shouldCollect('session') && ! $this->hasCollector('session')) { 616 | try { 617 | $this->addCollector(new SessionCollector($sessionManager)); 618 | } catch (\Exception $e) { 619 | $this->addThrowable( 620 | new Exception( 621 | 'Cannot add SessionCollector to Laravel Debugbar: ' . $e->getMessage(), 622 | $e->getCode(), 623 | $e 624 | ) 625 | ); 626 | } 627 | } 628 | } else { 629 | $sessionManager = null; 630 | } 631 | 632 | if ($this->shouldCollect('symfony_request', true) && !$this->hasCollector('request')) { 633 | try { 634 | $this->addCollector(new SymfonyRequestCollector($request, $response, $sessionManager)); 635 | } catch (\Exception $e) { 636 | $this->addThrowable( 637 | new Exception( 638 | 'Cannot add SymfonyRequestCollector to Laravel Debugbar: ' . $e->getMessage(), 639 | $e->getCode(), 640 | $e 641 | ) 642 | ); 643 | } 644 | } 645 | 646 | if ($app['config']->get('debugbar.clockwork') && ! $this->hasCollector('clockwork')) { 647 | 648 | try { 649 | $this->addCollector(new ClockworkCollector($request, $response, $sessionManager)); 650 | } catch (\Exception $e) { 651 | $this->addThrowable( 652 | new Exception( 653 | 'Cannot add ClockworkCollector to Laravel Debugbar: ' . $e->getMessage(), 654 | $e->getCode(), 655 | $e 656 | ) 657 | ); 658 | } 659 | 660 | $this->addClockworkHeaders($response); 661 | } 662 | 663 | if ($response->isRedirection()) { 664 | try { 665 | $this->stackData(); 666 | } catch (\Exception $e) { 667 | $app['log']->error('Debugbar exception: ' . $e->getMessage()); 668 | } 669 | } elseif ( 670 | $this->isJsonRequest($request) && 671 | $app['config']->get('debugbar.capture_ajax', true) 672 | ) { 673 | try { 674 | $this->sendDataInHeaders(true); 675 | 676 | if ($app['config']->get('debugbar.add_ajax_timing', false)) { 677 | $this->addServerTimingHeaders($response); 678 | } 679 | 680 | } catch (\Exception $e) { 681 | $app['log']->error('Debugbar exception: ' . $e->getMessage()); 682 | } 683 | } elseif ( 684 | ($response->headers->has('Content-Type') && 685 | strpos($response->headers->get('Content-Type'), 'html') === false) 686 | || $request->getRequestFormat() !== 'html' 687 | || $response->getContent() === false 688 | ) { 689 | try { 690 | // Just collect + store data, don't inject it. 691 | $this->collect(); 692 | } catch (\Exception $e) { 693 | $app['log']->error('Debugbar exception: ' . $e->getMessage()); 694 | } 695 | } elseif ($app['config']->get('debugbar.inject', true)) { 696 | try { 697 | $this->injectDebugbar($response); 698 | } catch (\Exception $e) { 699 | $app['log']->error('Debugbar exception: ' . $e->getMessage()); 700 | } 701 | } 702 | 703 | 704 | 705 | return $response; 706 | } 707 | 708 | /** 709 | * Check if the Debugbar is enabled 710 | * @return boolean 711 | */ 712 | public function isEnabled() 713 | { 714 | if ($this->enabled === null) { 715 | $this->enabled = value($this->app['config']->get('debugbar.enabled')); 716 | } 717 | 718 | return $this->enabled; 719 | } 720 | 721 | /** 722 | * Check if this is a request to the Debugbar OpenHandler 723 | * 724 | * @return bool 725 | */ 726 | protected function isDebugbarRequest() 727 | { 728 | return $this->app['request']->segment(1) == '_debugbar'; 729 | } 730 | 731 | /** 732 | * @param \Symfony\Component\HttpFoundation\Request $request 733 | * @return bool 734 | */ 735 | protected function isJsonRequest(Request $request) 736 | { 737 | // If XmlHttpRequest, return true 738 | if ($request->isXmlHttpRequest()) { 739 | return true; 740 | } 741 | 742 | // Check if the request wants Json 743 | $acceptable = $request->getAcceptableContentTypes(); 744 | return (isset($acceptable[0]) && $acceptable[0] == 'application/json'); 745 | } 746 | 747 | /** 748 | * Collects the data from the collectors 749 | * 750 | * @return array 751 | */ 752 | public function collect() 753 | { 754 | /** @var Request $request */ 755 | $request = $this->app['request']; 756 | 757 | $this->data = [ 758 | '__meta' => [ 759 | 'id' => $this->getCurrentRequestId(), 760 | 'datetime' => date('Y-m-d H:i:s'), 761 | 'utime' => microtime(true), 762 | 'method' => $request->getMethod(), 763 | 'uri' => $request->getRequestUri(), 764 | 'ip' => $request->getClientIp() 765 | ] 766 | ]; 767 | 768 | foreach ($this->collectors as $name => $collector) { 769 | $this->data[$name] = $collector->collect(); 770 | } 771 | 772 | // Remove all invalid (non UTF-8) characters 773 | array_walk_recursive( 774 | $this->data, 775 | function (&$item) { 776 | if (is_string($item) && !mb_check_encoding($item, 'UTF-8')) { 777 | $item = mb_convert_encoding($item, 'UTF-8', 'UTF-8'); 778 | } 779 | } 780 | ); 781 | 782 | if ($this->storage !== null) { 783 | $this->storage->save($this->getCurrentRequestId(), $this->data); 784 | } 785 | 786 | return $this->data; 787 | } 788 | 789 | /** 790 | * Injects the web debug toolbar into the given Response. 791 | * 792 | * @param \Symfony\Component\HttpFoundation\Response $response A Response instance 793 | * Based on https://github.com/symfony/WebProfilerBundle/blob/master/EventListener/WebDebugToolbarListener.php 794 | */ 795 | public function injectDebugbar(Response $response) 796 | { 797 | $content = $response->getContent(); 798 | 799 | $renderer = $this->getJavascriptRenderer(); 800 | if ($this->getStorage()) { 801 | $openHandlerUrl = route('debugbar.openhandler'); 802 | $renderer->setOpenHandlerUrl($openHandlerUrl); 803 | } 804 | 805 | $renderedContent = $renderer->renderHead() . $renderer->render(); 806 | 807 | $pos = strripos($content, ''); 808 | if (false !== $pos) { 809 | $content = substr($content, 0, $pos) . $renderedContent . substr($content, $pos); 810 | } else { 811 | $content = $content . $renderedContent; 812 | } 813 | 814 | // Update the new content and reset the content length 815 | $response->setContent($content); 816 | $response->headers->remove('Content-Length'); 817 | } 818 | 819 | /** 820 | * Disable the Debugbar 821 | */ 822 | public function disable() 823 | { 824 | $this->enabled = false; 825 | } 826 | 827 | /** 828 | * Adds a measure 829 | * 830 | * @param string $label 831 | * @param float $start 832 | * @param float $end 833 | */ 834 | public function addMeasure($label, $start, $end) 835 | { 836 | if ($this->hasCollector('time')) { 837 | /** @var \DebugBar\DataCollector\TimeDataCollector $collector */ 838 | $collector = $this->getCollector('time'); 839 | $collector->addMeasure($label, $start, $end); 840 | } 841 | } 842 | 843 | /** 844 | * Utility function to measure the execution of a Closure 845 | * 846 | * @param string $label 847 | * @param \Closure $closure 848 | */ 849 | public function measure($label, \Closure $closure) 850 | { 851 | if ($this->hasCollector('time')) { 852 | /** @var \DebugBar\DataCollector\TimeDataCollector $collector */ 853 | $collector = $this->getCollector('time'); 854 | $collector->measure($label, $closure); 855 | } else { 856 | $closure(); 857 | } 858 | } 859 | 860 | /** 861 | * Collect data in a CLI request 862 | * 863 | * @return array 864 | */ 865 | public function collectConsole() 866 | { 867 | if (!$this->isEnabled()) { 868 | return; 869 | } 870 | 871 | $this->data = [ 872 | '__meta' => [ 873 | 'id' => $this->getCurrentRequestId(), 874 | 'datetime' => date('Y-m-d H:i:s'), 875 | 'utime' => microtime(true), 876 | 'method' => 'CLI', 877 | 'uri' => isset($_SERVER['argv']) ? implode(' ', $_SERVER['argv']) : null, 878 | 'ip' => isset($_SERVER['SSH_CLIENT']) ? $_SERVER['SSH_CLIENT'] : null 879 | ] 880 | ]; 881 | 882 | foreach ($this->collectors as $name => $collector) { 883 | $this->data[$name] = $collector->collect(); 884 | } 885 | 886 | // Remove all invalid (non UTF-8) characters 887 | array_walk_recursive( 888 | $this->data, 889 | function (&$item) { 890 | if (is_string($item) && !mb_check_encoding($item, 'UTF-8')) { 891 | $item = mb_convert_encoding($item, 'UTF-8', 'UTF-8'); 892 | } 893 | } 894 | ); 895 | 896 | if ($this->storage !== null) { 897 | $this->storage->save($this->getCurrentRequestId(), $this->data); 898 | } 899 | 900 | return $this->data; 901 | } 902 | 903 | /** 904 | * Magic calls for adding messages 905 | * 906 | * @param string $method 907 | * @param array $args 908 | * @return mixed|void 909 | */ 910 | public function __call($method, $args) 911 | { 912 | $messageLevels = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug', 'log']; 913 | if (in_array($method, $messageLevels)) { 914 | foreach($args as $arg) { 915 | $this->addMessage($arg, $method); 916 | } 917 | } 918 | } 919 | 920 | /** 921 | * Adds a message to the MessagesCollector 922 | * 923 | * A message can be anything from an object to a string 924 | * 925 | * @param mixed $message 926 | * @param string $label 927 | */ 928 | public function addMessage($message, $label = 'info') 929 | { 930 | if ($this->hasCollector('messages')) { 931 | /** @var \DebugBar\DataCollector\MessagesCollector $collector */ 932 | $collector = $this->getCollector('messages'); 933 | $collector->addMessage($message, $label); 934 | } 935 | } 936 | 937 | /** 938 | * Check the version of Laravel 939 | * 940 | * @param string $version 941 | * @param string $operator (default: '>=') 942 | * @return boolean 943 | */ 944 | protected function checkVersion($version, $operator = ">=") 945 | { 946 | return version_compare($this->version, $version, $operator); 947 | } 948 | 949 | protected function isLumen() 950 | { 951 | return $this->is_lumen; 952 | } 953 | 954 | /** 955 | * @param DebugBar $debugbar 956 | */ 957 | protected function selectStorage(DebugBar $debugbar) 958 | { 959 | $config = $this->app['config']; 960 | if ($config->get('debugbar.storage.enabled')) { 961 | $driver = $config->get('debugbar.storage.driver', 'file'); 962 | 963 | switch ($driver) { 964 | case 'pdo': 965 | $connection = $config->get('debugbar.storage.connection'); 966 | $table = $this->app['db']->getTablePrefix() . 'phpdebugbar'; 967 | $pdo = $this->app['db']->connection($connection)->getPdo(); 968 | $storage = new PdoStorage($pdo, $table); 969 | break; 970 | case 'redis': 971 | $connection = $config->get('debugbar.storage.connection'); 972 | $client = $this->app['redis']->connection($connection); 973 | if (is_a($client, 'Illuminate\Redis\Connections\PredisConnection', false)) { 974 | $client = $client->client(); 975 | } 976 | $storage = new RedisStorage($client); 977 | break; 978 | case 'custom': 979 | $class = $config->get('debugbar.storage.provider'); 980 | $storage = $this->app->make($class); 981 | break; 982 | case 'file': 983 | default: 984 | $path = $config->get('debugbar.storage.path'); 985 | $storage = new FilesystemStorage($this->app['files'], $path); 986 | break; 987 | } 988 | 989 | $debugbar->setStorage($storage); 990 | } 991 | } 992 | 993 | protected function addClockworkHeaders(Response $response) 994 | { 995 | $prefix = $this->app['config']->get('debugbar.route_prefix'); 996 | $response->headers->set('X-Clockwork-Id', $this->getCurrentRequestId(), true); 997 | $response->headers->set('X-Clockwork-Version', 1, true); 998 | $response->headers->set('X-Clockwork-Path', $prefix .'/clockwork/', true); 999 | } 1000 | 1001 | /** 1002 | * Add Server-Timing headers for the TimeData collector 1003 | * 1004 | * @see https://www.w3.org/TR/server-timing/ 1005 | * @param Response $response 1006 | */ 1007 | protected function addServerTimingHeaders(Response $response) 1008 | { 1009 | if ($this->hasCollector('time')) { 1010 | $collector = $this->getCollector('time'); 1011 | 1012 | $headers = []; 1013 | foreach ($collector->collect()['measures'] as $k => $m) { 1014 | $headers[] = sprintf('%d=%F; "%s"', $k, $m['duration'], str_replace('"', "'", $m['label'])); 1015 | } 1016 | 1017 | $response->headers->set('Server-Timing', $headers, false); 1018 | } 1019 | } 1020 | } 1021 | --------------------------------------------------------------------------------