diff --git a/composer.json b/composer.json index b9a681bd1..cc0db9901 100644 --- a/composer.json +++ b/composer.json @@ -155,6 +155,7 @@ "Symfony\\Component\\Console\\Output\\": "overrides/symfony/console/Output/", "DebugBar\\": "overrides/maximebf/debugbar/src/DebugBar/", "DebugBar\\DataFormatter\\": "overrides/maximebf/debugbar/src/DataFormatter/DataFormatter/", + "DebugBar\\DataCollector\\PDO\\": "overrides/maximebf/debugbar/src/DataFormatter/DataCollector/PDO/", "Illuminate\\Cache\\Console\\": "overrides/laravel/framework/src/Illuminate/Cache/Console/", "Dotenv\\": "overrides/vlucas/phpdotenv/src/", "Illuminate\\View\\": "overrides/laravel/framework/src/Illuminate/View/", @@ -170,6 +171,7 @@ "Devfactory\\Minify\\Providers\\": "overrides/devfactory/minify/src/Providers/", "Barryvdh\\Debugbar\\": "overrides/barryvdh/laravel-debugbar/src/", "Barryvdh\\Debugbar\\DataFormatter\\": "overrides/barryvdh/laravel-debugbar/src/DataFormatter/", + "Barryvdh\\Debugbar\\DataCollector\\": "overrides/barryvdh/laravel-debugbar/src/DataCollector/", "Symfony\\Component\\Process\\": "overrides/symfony/process/", "Symfony\\Component\\HttpKernel\\": "overrides/symfony/http-kernel/", "Symfony\\Component\\HttpKernel\\Exception\\": "overrides/symfony/http-kernel/Exception/", @@ -195,6 +197,7 @@ "League\\Flysystem\\": "overrides/league/flysystem/src/", "Whoops\\": "overrides/filp/whoops/src/Whoops/", "Whoops\\Handler\\": "overrides/filp/whoops/src/Whoops/Handler/", + "Whoops\\Util\\": "overrides/filp/whoops/src/Whoops/Util/", "Mews\\Purifier\\": "overrides/mews/purifier/src/", "Cron\\": "overrides/mtdowling/cron-expression/src/Cron/" }, @@ -308,6 +311,7 @@ "vendor/maximebf/debugbar/src/DebugBar/DebugBar.php", "vendor/maximebf/debugbar/src/DebugBar/JavascriptRenderer.php", "vendor/maximebf/debugbar/src/DebugBar/DataFormatter/DataFormatter.php", + "vendor/maximebf/debugbar/src/DebugBar/DataCollector/PDO/PDOCollector.php", "vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php", "vendor/vlucas/phpdotenv/src/Loader.php", "vendor/laravel/framework/src/Illuminate/View/View.php", @@ -319,9 +323,11 @@ "vendor/symfony/routing/CompiledRoute.php", "vendor/symfony/var-dumper/Cloner/Data.php", "vendor/symfony/var-dumper/Cloner/Stub.php", + "vendor/symfony/var-dumper/Cloner/AbstractCloner.php", "vendor/symfony/var-dumper/Dumper/HtmlDumper.php", "vendor/devfactory/minify/src/Providers/BaseProvider.php", "vendor/barryvdh/laravel-debugbar/src/DataFormatter/QueryFormatter.php", + "vendor/barryvdh/laravel-debugbar/src/DataCollector/QueryCollector.php", "vendor/symfony/process/Process.php", "vendor/symfony/http-kernel/UriSigner.php", "vendor/symfony/http-kernel/Exception/HttpException.php", @@ -432,6 +438,7 @@ "vendor/laravel/framework/src/Illuminate/Mail/Mailer.php", "vendor/filp/whoops/src/Whoops/Run.php", "vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php", + "vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php", "vendor/laravel/framework/src/Illuminate/Validation/Factory.php", "vendor/laravel/framework/src/Illuminate/Validation/ValidationRuleParser.php", "vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php" diff --git a/overrides/barryvdh/laravel-debugbar/src/DataCollector/QueryCollector.php b/overrides/barryvdh/laravel-debugbar/src/DataCollector/QueryCollector.php new file mode 100644 index 000000000..8bec036be --- /dev/null +++ b/overrides/barryvdh/laravel-debugbar/src/DataCollector/QueryCollector.php @@ -0,0 +1,477 @@ +timeCollector = $timeCollector; + } + + /** + * Renders the SQL of traced statements with params embedded + * + * @param boolean $enabled + * @param string $quotationChar NOT USED + */ + public function setRenderSqlWithParams($enabled = true, $quotationChar = "'") + { + $this->renderSqlWithParams = $enabled; + } + + /** + * Show or hide the hints in the parameters + * + * @param boolean $enabled + */ + public function setShowHints($enabled = true) + { + $this->showHints = $enabled; + } + + /** + * Enable/disable finding the source + * + * @param bool $value + * @param array $middleware + */ + public function setFindSource($value, array $middleware) + { + $this->findSource = (bool) $value; + $this->middleware = $middleware; + } + + /** + * Enable/disable the EXPLAIN queries + * + * @param bool $enabled + * @param array|null $types Array of types to explain queries (select/insert/update/delete) + */ + public function setExplainSource($enabled, $types) + { + $this->explainQuery = $enabled; + if($types){ + $this->explainTypes = $types; + } + } + + /** + * + * @param string $query + * @param array $bindings + * @param float $time + * @param \Illuminate\Database\Connection $connection + */ + public function addQuery($query, $bindings, $time, $connection) + { + $explainResults = []; + $time = $time / 1000; + $endTime = microtime(true); + $startTime = $endTime - $time; + $hints = $this->performQueryAnalysis($query); + + $pdo = $connection->getPdo(); + $bindings = $connection->prepareBindings($bindings); + + // Run EXPLAIN on this query (if needed) + if ($this->explainQuery && preg_match('/^('.implode($this->explainTypes).') /i', $query)) { + $statement = $pdo->prepare('EXPLAIN ' . $query); + $statement->execute($bindings); + $explainResults = $statement->fetchAll(\PDO::FETCH_CLASS); + } + + $bindings = $this->getDataFormatter()->checkBindings($bindings); + if (!empty($bindings) && $this->renderSqlWithParams) { + foreach ($bindings as $key => $binding) { + // This regex matches placeholders only, not the question marks, + // nested in quotes, while we iterate through the bindings + // and substitute placeholders by suitable values. + $regex = is_numeric($key) + ? "/\?(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/" + : "/:{$key}(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/"; + $query = preg_replace($regex, $pdo->quote($binding), $query, 1); + } + } + + $source = []; + + if ($this->findSource) { + try { + $source = $this->findSource(); + } catch (\Exception $e) { + } + } + + $this->queries[] = [ + 'query' => $query, + 'type' => 'query', + 'bindings' => $this->getDataFormatter()->escapeBindings($bindings), + 'time' => $time, + 'source' => $source, + 'explain' => $explainResults, + 'connection' => $connection->getDatabaseName(), + 'hints' => $this->showHints ? $hints : null, + ]; + + if ($this->timeCollector !== null) { + $this->timeCollector->addMeasure($query, $startTime, $endTime); + } + } + + /** + * Explainer::performQueryAnalysis() + * + * Perform simple regex analysis on the code + * + * @package xplain (https://github.com/rap2hpoutre/mysql-xplain-xplain) + * @author e-doceo + * @copyright 2014 + * @version $Id$ + * @access public + * @param string $query + * @return string + */ + protected function performQueryAnalysis($query) + { + $hints = []; + if (preg_match('/^\\s*SELECT\\s*`?[a-zA-Z0-9]*`?\\.?\\*/i', $query)) { + $hints[] = 'Use SELECT * only if you need all columns from table'; + } + if (preg_match('/ORDER BY RAND()/i', $query)) { + $hints[] = 'ORDER BY RAND() is slow, try to avoid if you can. + You can read this + or this'; + } + if (strpos($query, '!=') !== false) { + $hints[] = 'The != operator is not standard. Use the <> operator to test for inequality instead.'; + } + if (stripos($query, 'WHERE') === false && preg_match('/^(SELECT) /i', $query)) { + $hints[] = 'The SELECT statement has no WHERE clause and could examine many more rows than intended'; + } + if (preg_match('/LIMIT\\s/i', $query) && stripos($query, 'ORDER BY') === false) { + $hints[] = 'LIMIT without ORDER BY causes non-deterministic results, depending on the query execution plan'; + } + if (preg_match('/LIKE\\s[\'"](%.*?)[\'"]/i', $query, $matches)) { + $hints[] = 'An argument has a leading wildcard character: ' . $matches[1]. '. + The predicate with this argument is not sargable and cannot use an index if one exists.'; + } + return $hints; + } + + /** + * Use a backtrace to search for the origins of the query. + * + * @return array + */ + protected function findSource() + { + $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT, 50); + + $sources = []; + + foreach ($stack as $index => $trace) { + $sources[] = $this->parseTrace($index, $trace); + } + + return array_filter($sources); + } + + /** + * Parse a trace element from the backtrace stack. + * + * @param int $index + * @param array $trace + * @return object|bool + */ + protected function parseTrace($index, array $trace) + { + $frame = (object) [ + 'index' => $index, + 'namespace' => null, + 'name' => null, + 'line' => isset($trace['line']) ? $trace['line'] : '?', + ]; + + if (isset($trace['function']) && $trace['function'] == 'substituteBindings') { + $frame->name = 'Route binding'; + + return $frame; + } + + if (isset($trace['class']) && + isset($trace['file']) && + !$this->fileIsInExcludedPath($trace['file']) + ) { + $file = $trace['file']; + + if (isset($trace['object']) && is_a($trace['object'], 'Twig_Template')) { + list($file, $frame->line) = $this->getTwigInfo($trace); + } elseif (strpos($file, storage_path()) !== false) { + $hash = pathinfo($file, PATHINFO_FILENAME); + + if (! $frame->name = $this->findViewFromHash($hash)) { + $frame->name = $hash; + } + + $frame->namespace = 'view'; + + return $frame; + } elseif (strpos($file, 'Middleware') !== false) { + $frame->name = $this->findMiddlewareFromFile($file); + + if ($frame->name) { + $frame->namespace = 'middleware'; + } else { + $frame->name = $this->normalizeFilename($file); + } + + return $frame; + } + + $frame->name = $this->normalizeFilename($file); + + return $frame; + } + + + return false; + } + + /** + * Check if the given file is to be excluded from analysis + * + * @param string $file + * @return bool + */ + protected function fileIsInExcludedPath($file) + { + $excludedPaths = [ + '/vendor/laravel/framework/src/Illuminate/Database', + '/vendor/laravel/framework/src/Illuminate/Events', + '/vendor/barryvdh/laravel-debugbar', + ]; + + $normalizedPath = str_replace('\\', '/', $file); + + foreach ($excludedPaths as $excludedPath) { + if (strpos($normalizedPath, $excludedPath) !== false) { + return true; + } + } + + return false; + } + + /** + * Find the middleware alias from the file. + * + * @param string $file + * @return string|null + */ + protected function findMiddlewareFromFile($file) + { + $filename = pathinfo($file, PATHINFO_FILENAME); + + foreach ($this->middleware as $alias => $class) { + if (strpos($class, $filename) !== false) { + return $alias; + } + } + } + + /** + * Find the template name from the hash. + * + * @param string $hash + * @return null|string + */ + protected function findViewFromHash($hash) + { + $finder = app('view')->getFinder(); + + if (isset($this->reflection['viewfinderViews'])) { + $property = $this->reflection['viewfinderViews']; + } else { + $reflection = new \ReflectionClass($finder); + $property = $reflection->getProperty('views'); + $property->setAccessible(true); + $this->reflection['viewfinderViews'] = $property; + } + + foreach ($property->getValue($finder) as $name => $path){ + if (sha1($path) == $hash || md5($path) == $hash) { + return $name; + } + } + } + + /** + * Get the filename/line from a Twig template trace + * + * @param array $trace + * @return array The file and line + */ + protected function getTwigInfo($trace) + { + $file = $trace['object']->getTemplateName(); + + if (isset($trace['line'])) { + foreach ($trace['object']->getDebugInfo() as $codeLine => $templateLine) { + if ($codeLine <= $trace['line']) { + return [$file, $templateLine]; + } + } + } + + return [$file, -1]; + } + + /** + * Shorten the path by removing the relative links and base dir + * + * @param string $path + * @return string + */ + protected function normalizeFilename($path) + { + if (file_exists($path)) { + $path = realpath($path); + } + return str_replace(base_path(), '', $path); + } + + /** + * Collect a database transaction event. + * @param string $event + * @param \Illuminate\Database\Connection $connection + * @return array + */ + public function collectTransactionEvent($event, $connection) + { + $source = []; + + if ($this->findSource) { + try { + $source = $this->findSource(); + } catch (\Exception $e) { + } + } + + $this->queries[] = [ + 'query' => $event, + 'type' => 'transaction', + 'bindings' => [], + 'time' => 0, + 'source' => $source, + 'explain' => [], + 'connection' => $connection->getDatabaseName(), + 'hints' => null, + ]; + } + + /** + * Reset the queries. + */ + public function reset() + { + $this->queries = []; + } + + /** + * {@inheritDoc} + */ + public function collect() + { + $totalTime = 0; + $queries = $this->queries; + + $statements = []; + foreach ($queries as $query) { + $totalTime += $query['time']; + + $statements[] = [ + 'sql' => $this->getDataFormatter()->formatSql($query['query']), + 'type' => $query['type'], + 'params' => [], + 'bindings' => $query['bindings'], + 'hints' => $query['hints'], + 'backtrace' => array_values($query['source']), + 'duration' => $query['time'], + 'duration_str' => ($query['type'] == 'transaction') ? '' : $this->formatDuration($query['time']), + 'stmt_id' => $this->getDataFormatter()->formatSource(reset($query['source'])), + 'connection' => $query['connection'], + ]; + + //Add the results from the explain as new rows + foreach($query['explain'] as $explain){ + $statements[] = [ + 'sql' => ' - EXPLAIN #' . $explain->id . ': `' . $explain->table . '` (' . $explain->select_type . ')', + 'type' => 'explain', + 'params' => $explain, + 'row_count' => $explain->rows, + 'stmt_id' => $explain->id, + ]; + } + } + + $nb_statements = array_filter($queries, function ($query) { + return $query['type'] == 'query'; + }); + + $data = [ + 'nb_statements' => count($nb_statements), + 'nb_failed_statements' => 0, + 'accumulated_duration' => $totalTime, + 'accumulated_duration_str' => $this->formatDuration($totalTime), + 'statements' => $statements + ]; + return $data; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'queries'; + } + + /** + * {@inheritDoc} + */ + public function getWidgets() + { + return [ + "queries" => [ + "icon" => "database", + "widget" => "PhpDebugBar.Widgets.LaravelSQLQueriesWidget", + "map" => "queries", + "default" => "[]" + ], + "queries:badge" => [ + "map" => "queries.nb_statements", + "default" => 0 + ] + ]; + } +} diff --git a/overrides/filp/whoops/src/Whoops/Util/TemplateHelper.php b/overrides/filp/whoops/src/Whoops/Util/TemplateHelper.php new file mode 100644 index 000000000..277f8e419 --- /dev/null +++ b/overrides/filp/whoops/src/Whoops/Util/TemplateHelper.php @@ -0,0 +1,352 @@ + + */ + +namespace Whoops\Util; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\AbstractCloner; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Whoops\Exception\Frame; + +/** + * Exposes useful tools for working with/in templates + */ +class TemplateHelper +{ + /** + * An array of variables to be passed to all templates + * @var array + */ + private $variables = []; + + /** + * @var HtmlDumper + */ + private $htmlDumper; + + /** + * @var HtmlDumperOutput + */ + private $htmlDumperOutput; + + /** + * @var AbstractCloner + */ + private $cloner; + + /** + * @var string + */ + private $applicationRootPath; + + public function __construct() + { + // root path for ordinary composer projects + $this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + } + + /** + * Escapes a string for output in an HTML document + * + * @param string $raw + * @return string + */ + public function escape($raw) + { + $flags = ENT_QUOTES; + + // HHVM has all constants defined, but only ENT_IGNORE + // works at the moment + if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) { + $flags |= ENT_SUBSTITUTE; + } else { + // This is for 5.3. + // The documentation warns of a potential security issue, + // but it seems it does not apply in our case, because + // we do not blacklist anything anywhere. + $flags |= ENT_IGNORE; + } + + $raw = str_replace(chr(9), ' ', $raw); + + return htmlspecialchars($raw, $flags, "UTF-8"); + } + + /** + * Escapes a string for output in an HTML document, but preserves + * URIs within it, and converts them to clickable anchor elements. + * + * @param string $raw + * @return string + */ + public function escapeButPreserveUris($raw) + { + $escaped = $this->escape($raw); + return preg_replace( + "@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@", + "$1", + $escaped + ); + } + + /** + * Makes sure that the given string breaks on the delimiter. + * + * @param string $delimiter + * @param string $s + * @return string + */ + public function breakOnDelimiter($delimiter, $s) + { + $parts = explode($delimiter, $s); + foreach ($parts as &$part) { + $part = '' . $part . ''; + } + + return implode($delimiter, $parts); + } + + /** + * Replace the part of the path that all files have in common. + * + * @param string $path + * @return string + */ + public function shorten($path) + { + if ($this->applicationRootPath != "/") { + $path = str_replace($this->applicationRootPath, '…', $path); + } + + return $path; + } + + private function getDumper() + { + if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) { + $this->htmlDumperOutput = new HtmlDumperOutput(); + // re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump. + $this->htmlDumper = new HtmlDumper($this->htmlDumperOutput); + + $styles = [ + 'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal', + 'num' => 'color:#BCD42A', + 'const' => 'color: #4bb1b1;', + 'str' => 'color:#BCD42A', + 'note' => 'color:#ef7c61', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#FFFFFF', + 'key' => 'color:#BCD42A', + 'index' => 'color:#ef7c61', + ]; + $this->htmlDumper->setStyles($styles); + } + + return $this->htmlDumper; + } + + /** + * Format the given value into a human readable string. + * + * @param mixed $value + * @return string + */ + public function dump($value) + { + $dumper = $this->getDumper(); + + if ($dumper) { + // re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump. + // exclude verbose information (e.g. exception stack traces) + if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) { + $cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE); + // Symfony VarDumper 2.6 Caster class dont exist. + } else { + $cloneVar = $this->getCloner()->cloneVar($value); + } + + $dumper->dump( + $cloneVar, + $this->htmlDumperOutput + ); + + $output = $this->htmlDumperOutput->getOutput(); + $this->htmlDumperOutput->clear(); + + return $output; + } + + return htmlspecialchars(print_r($value, true)); + } + + /** + * Format the args of the given Frame as a human readable html string + * + * @param Frame $frame + * @return string the rendered html + */ + public function dumpArgs(Frame $frame) + { + // we support frame args only when the optional dumper is available + if (!$this->getDumper()) { + return ''; + } + + $html = ''; + $numFrames = count($frame->getArgs()); + + if ($numFrames > 0) { + $html = '
    '; + foreach ($frame->getArgs() as $j => $frameArg) { + $html .= '
  1. '. $this->dump($frameArg) .'
  2. '; + } + $html .= '
'; + } + + return $html; + } + + /** + * Convert a string to a slug version of itself + * + * @param string $original + * @return string + */ + public function slug($original) + { + $slug = str_replace(" ", "-", $original); + $slug = preg_replace('/[^\w\d\-\_]/i', '', $slug); + return strtolower($slug); + } + + /** + * Given a template path, render it within its own scope. This + * method also accepts an array of additional variables to be + * passed to the template. + * + * @param string $template + * @param array $additionalVariables + */ + public function render($template, ?array $additionalVariables = null) + { + $variables = $this->getVariables(); + + // Pass the helper to the template: + $variables["tpl"] = $this; + + if ($additionalVariables !== null) { + $variables = array_replace($variables, $additionalVariables); + } + + call_user_func(function () { + extract(func_get_arg(1)); + require func_get_arg(0); + }, $template, $variables); + } + + /** + * Sets the variables to be passed to all templates rendered + * by this template helper. + * + * @param array $variables + */ + public function setVariables(array $variables) + { + $this->variables = $variables; + } + + /** + * Sets a single template variable, by its name: + * + * @param string $variableName + * @param mixed $variableValue + */ + public function setVariable($variableName, $variableValue) + { + $this->variables[$variableName] = $variableValue; + } + + /** + * Gets a single template variable, by its name, or + * $defaultValue if the variable does not exist + * + * @param string $variableName + * @param mixed $defaultValue + * @return mixed + */ + public function getVariable($variableName, $defaultValue = null) + { + return isset($this->variables[$variableName]) ? + $this->variables[$variableName] : $defaultValue; + } + + /** + * Unsets a single template variable, by its name + * + * @param string $variableName + */ + public function delVariable($variableName) + { + unset($this->variables[$variableName]); + } + + /** + * Returns all variables for this helper + * + * @return array + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Set the cloner used for dumping variables. + * + * @param AbstractCloner $cloner + */ + public function setCloner($cloner) + { + $this->cloner = $cloner; + } + + /** + * Get the cloner used for dumping variables. + * + * @return AbstractCloner + */ + public function getCloner() + { + if (!$this->cloner) { + $this->cloner = new VarCloner(); + } + return $this->cloner; + } + + /** + * Set the application root path. + * + * @param string $applicationRootPath + */ + public function setApplicationRootPath($applicationRootPath) + { + $this->applicationRootPath = $applicationRootPath; + } + + /** + * Return the application root path. + * + * @return string + */ + public function getApplicationRootPath() + { + return $this->applicationRootPath; + } +} diff --git a/overrides/maximebf/debugbar/src/DebugBar/DataCollector/PDO/PDOCollector.php b/overrides/maximebf/debugbar/src/DebugBar/DataCollector/PDO/PDOCollector.php new file mode 100644 index 000000000..fcf79b715 --- /dev/null +++ b/overrides/maximebf/debugbar/src/DebugBar/DataCollector/PDO/PDOCollector.php @@ -0,0 +1,206 @@ +'; + + /** + * @param TraceablePDO $pdo + * @param TimeDataCollector $timeCollector + */ + public function __construct(?TraceablePDO $pdo = null, ?TimeDataCollector $timeCollector = null) + { + $this->timeCollector = $timeCollector; + if ($pdo !== null) { + $this->addConnection($pdo, 'default'); + } + } + + /** + * Renders the SQL of traced statements with params embeded + * + * @param boolean $enabled + */ + public function setRenderSqlWithParams($enabled = true, $quotationChar = '<>') + { + $this->renderSqlWithParams = $enabled; + $this->sqlQuotationChar = $quotationChar; + } + + /** + * @return bool + */ + public function isSqlRenderedWithParams() + { + return $this->renderSqlWithParams; + } + + /** + * @return string + */ + public function getSqlQuotationChar() + { + return $this->sqlQuotationChar; + } + + /** + * Adds a new PDO instance to be collector + * + * @param TraceablePDO $pdo + * @param string $name Optional connection name + */ + public function addConnection(TraceablePDO $pdo, $name = null) + { + if ($name === null) { + $name = spl_object_hash($pdo); + } + $this->connections[$name] = $pdo; + } + + /** + * Returns PDO instances to be collected + * + * @return array + */ + public function getConnections() + { + return $this->connections; + } + + /** + * @return array + */ + public function collect() + { + $data = array( + 'nb_statements' => 0, + 'nb_failed_statements' => 0, + 'accumulated_duration' => 0, + 'memory_usage' => 0, + 'peak_memory_usage' => 0, + 'statements' => array() + ); + + foreach ($this->connections as $name => $pdo) { + $pdodata = $this->collectPDO($pdo, $this->timeCollector, $name); + $data['nb_statements'] += $pdodata['nb_statements']; + $data['nb_failed_statements'] += $pdodata['nb_failed_statements']; + $data['accumulated_duration'] += $pdodata['accumulated_duration']; + $data['memory_usage'] += $pdodata['memory_usage']; + $data['peak_memory_usage'] = max($data['peak_memory_usage'], $pdodata['peak_memory_usage']); + $data['statements'] = array_merge($data['statements'], + array_map(function ($s) use ($name) { $s['connection'] = $name; return $s; }, $pdodata['statements'])); + } + + $data['accumulated_duration_str'] = $this->getDataFormatter()->formatDuration($data['accumulated_duration']); + $data['memory_usage_str'] = $this->getDataFormatter()->formatBytes($data['memory_usage']); + $data['peak_memory_usage_str'] = $this->getDataFormatter()->formatBytes($data['peak_memory_usage']); + + return $data; + } + + /** + * Collects data from a single TraceablePDO instance + * + * @param TraceablePDO $pdo + * @param TimeDataCollector $timeCollector + * @param string|null $connectionName the pdo connection (eg default | read | write) + * @return array + */ + protected function collectPDO(TraceablePDO $pdo, ?TimeDataCollector $timeCollector = null, $connectionName = null) + { + if (empty($connectionName) || $connectionName == 'default') { + $connectionName = 'pdo'; + } else { + $connectionName = 'pdo ' . $connectionName; + } + $stmts = array(); + foreach ($pdo->getExecutedStatements() as $stmt) { + $stmts[] = array( + 'sql' => $this->renderSqlWithParams ? $stmt->getSqlWithParams($this->sqlQuotationChar) : $stmt->getSql(), + 'row_count' => $stmt->getRowCount(), + 'stmt_id' => $stmt->getPreparedId(), + 'prepared_stmt' => $stmt->getSql(), + 'params' => (object) $stmt->getParameters(), + 'duration' => $stmt->getDuration(), + 'duration_str' => $this->getDataFormatter()->formatDuration($stmt->getDuration()), + 'memory' => $stmt->getMemoryUsage(), + 'memory_str' => $this->getDataFormatter()->formatBytes($stmt->getMemoryUsage()), + 'end_memory' => $stmt->getEndMemory(), + 'end_memory_str' => $this->getDataFormatter()->formatBytes($stmt->getEndMemory()), + 'is_success' => $stmt->isSuccess(), + 'error_code' => $stmt->getErrorCode(), + 'error_message' => $stmt->getErrorMessage() + ); + if ($timeCollector !== null) { + $timeCollector->addMeasure($stmt->getSql(), $stmt->getStartTime(), $stmt->getEndTime(), array(), $connectionName); + } + } + + return array( + 'nb_statements' => count($stmts), + 'nb_failed_statements' => count($pdo->getFailedExecutedStatements()), + 'accumulated_duration' => $pdo->getAccumulatedStatementsDuration(), + 'accumulated_duration_str' => $this->getDataFormatter()->formatDuration($pdo->getAccumulatedStatementsDuration()), + 'memory_usage' => $pdo->getMemoryUsage(), + 'memory_usage_str' => $this->getDataFormatter()->formatBytes($pdo->getPeakMemoryUsage()), + 'peak_memory_usage' => $pdo->getPeakMemoryUsage(), + 'peak_memory_usage_str' => $this->getDataFormatter()->formatBytes($pdo->getPeakMemoryUsage()), + 'statements' => $stmts + ); + } + + /** + * @return string + */ + public function getName() + { + return 'pdo'; + } + + /** + * @return array + */ + public function getWidgets() + { + return array( + "database" => array( + "icon" => "database", + "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget", + "map" => "pdo", + "default" => "[]" + ), + "database:badge" => array( + "map" => "pdo.nb_statements", + "default" => 0 + ) + ); + } + + /** + * @return array + */ + public function getAssets() + { + return array( + 'css' => 'widgets/sqlqueries/widget.css', + 'js' => 'widgets/sqlqueries/widget.js' + ); + } +} diff --git a/overrides/symfony/var-dumper/Cloner/AbstractCloner.php b/overrides/symfony/var-dumper/Cloner/AbstractCloner.php new file mode 100644 index 000000000..7c27d637c --- /dev/null +++ b/overrides/symfony/var-dumper/Cloner/AbstractCloner.php @@ -0,0 +1,332 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * AbstractCloner implements a generic caster mechanism for objects and resources. + * + * @author Nicolas Grekas + */ +abstract class AbstractCloner implements ClonerInterface +{ + public static $defaultCasters = array( + '__PHP_Incomplete_Class' => array('Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'), + + 'Symfony\Component\VarDumper\Caster\CutStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'), + 'Symfony\Component\VarDumper\Caster\CutArrayStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'), + 'Symfony\Component\VarDumper\Caster\ConstStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'), + 'Symfony\Component\VarDumper\Caster\EnumStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'), + + 'Closure' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'), + 'Generator' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'), + 'ReflectionType' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'), + 'ReflectionGenerator' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'), + 'ReflectionClass' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'), + 'ReflectionFunctionAbstract' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'), + 'ReflectionMethod' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'), + 'ReflectionParameter' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'), + 'ReflectionProperty' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'), + 'ReflectionExtension' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'), + 'ReflectionZendExtension' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'), + + 'Doctrine\Common\Persistence\ObjectManager' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Doctrine\Common\Proxy\Proxy' => array('Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'), + 'Doctrine\ORM\Proxy\Proxy' => array('Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'), + 'Doctrine\ORM\PersistentCollection' => array('Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'), + + 'DOMException' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'), + 'DOMStringList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMNameList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMImplementation' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'), + 'DOMImplementationList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMNode' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'), + 'DOMNameSpaceNode' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'), + 'DOMDocument' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'), + 'DOMNodeList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMNamedNodeMap' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMCharacterData' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'), + 'DOMAttr' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'), + 'DOMElement' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'), + 'DOMText' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'), + 'DOMTypeinfo' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'), + 'DOMDomError' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'), + 'DOMLocator' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'), + 'DOMDocumentType' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'), + 'DOMNotation' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'), + 'DOMEntity' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'), + 'DOMProcessingInstruction' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'), + 'DOMXPath' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'), + + 'XmlReader' => array('Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'), + + 'ErrorException' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'), + 'Exception' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'), + 'Error' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'), + 'Symfony\Component\DependencyInjection\ContainerInterface' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Symfony\Component\HttpFoundation\Request' => array('Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'), + 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'), + 'Symfony\Component\VarDumper\Caster\TraceStub' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'), + 'Symfony\Component\VarDumper\Caster\FrameStub' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'), + 'Symfony\Component\Debug\Exception\SilencedErrorContext' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'), + + 'PHPUnit_Framework_MockObject_MockObject' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Prophecy\Prophecy\ProphecySubjectInterface' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Mockery\MockInterface' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + + 'PDO' => array('Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'), + 'PDOStatement' => array('Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'), + + 'AMQPConnection' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'), + 'AMQPChannel' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'), + 'AMQPQueue' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'), + 'AMQPExchange' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'), + 'AMQPEnvelope' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'), + + 'ArrayObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'), + 'ArrayIterator' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'), + 'SplDoublyLinkedList' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'), + 'SplFileInfo' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'), + 'SplFileObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'), + 'SplFixedArray' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFixedArray'), + 'SplHeap' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'), + 'SplObjectStorage' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'), + 'SplPriorityQueue' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'), + 'OuterIterator' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'), + + 'MongoCursorInterface' => array('Symfony\Component\VarDumper\Caster\MongoCaster', 'castCursor'), + + 'Redis' => array('Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'), + 'RedisArray' => array('Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'), + + 'DateTimeInterface' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'), + 'DateInterval' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'), + 'DateTimeZone' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'), + 'DatePeriod' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'), + + ':curl' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'), + ':dba' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'), + ':dba persistent' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'), + ':gd' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'), + ':mysql link' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'), + ':pgsql large object' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'), + ':pgsql link' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'), + ':pgsql link persistent' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'), + ':pgsql result' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'), + ':process' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'), + ':stream' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'), + ':persistent stream' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'), + ':stream-context' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'), + ':xml' => array('Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'), + ); + + protected $maxItems = 2500; + protected $maxString = -1; + protected $minDepth = 1; + protected $useExt; + + private $casters = array(); + private $prevErrorHandler; + private $classInfo = array(); + private $filter = 0; + + /** + * @param callable[]|null $casters A map of casters + * + * @see addCasters + */ + public function __construct(?array $casters = null) + { + if (null === $casters) { + $casters = static::$defaultCasters; + } + $this->addCasters($casters); + $this->useExt = \extension_loaded('symfony_debug'); + } + + /** + * Adds casters for resources and objects. + * + * Maps resources or objects types to a callback. + * Types are in the key, with a callable caster for value. + * Resource types are to be prefixed with a `:`, + * see e.g. static::$defaultCasters. + * + * @param callable[] $casters A map of casters + */ + public function addCasters(array $casters) + { + foreach ($casters as $type => $callback) { + $this->casters[strtolower($type)][] = \is_string($callback) && false !== strpos($callback, '::') ? explode('::', $callback, 2) : $callback; + } + } + + /** + * Sets the maximum number of items to clone past the minimum depth in nested structures. + * + * @param int $maxItems + */ + public function setMaxItems($maxItems) + { + $this->maxItems = (int) $maxItems; + } + + /** + * Sets the maximum cloned length for strings. + * + * @param int $maxString + */ + public function setMaxString($maxString) + { + $this->maxString = (int) $maxString; + } + + /** + * Sets the minimum tree depth where we are guaranteed to clone all the items. After this + * depth is reached, only setMaxItems items will be cloned. + * + * @param int $minDepth + */ + public function setMinDepth($minDepth) + { + $this->minDepth = (int) $minDepth; + } + + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * @param int $filter A bit field of Caster::EXCLUDE_* constants + * + * @return Data The cloned variable represented by a Data object + */ + public function cloneVar($var, $filter = 0) + { + $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) { + if (E_RECOVERABLE_ERROR === $type || E_USER_ERROR === $type) { + // Cloner never dies + throw new \ErrorException($msg, 0, $type, $file, $line); + } + + if ($this->prevErrorHandler) { + return \call_user_func($this->prevErrorHandler, $type, $msg, $file, $line, $context); + } + + return false; + }); + $this->filter = $filter; + + if ($gc = gc_enabled()) { + gc_disable(); + } + try { + return new Data($this->doClone($var)); + } finally { + if ($gc) { + gc_enable(); + } + restore_error_handler(); + $this->prevErrorHandler = null; + } + } + + /** + * Effectively clones the PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return array The cloned variable represented in an array + */ + abstract protected function doClone($var); + + /** + * Casts an object to an array representation. + * + * @param Stub $stub The Stub for the casted object + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array The object casted as array + */ + protected function castObject(Stub $stub, $isNested) + { + $obj = $stub->value; + $class = $stub->class; + + if (isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00")) { + $stub->class = get_parent_class($class).'@anonymous'; + } + if (isset($this->classInfo[$class])) { + list($i, $parents, $hasDebugInfo) = $this->classInfo[$class]; + } else { + $i = 2; + $parents = array(strtolower($class)); + $hasDebugInfo = method_exists($class, '__debugInfo'); + + foreach (class_parents($class) as $p) { + $parents[] = strtolower($p); + ++$i; + } + foreach (class_implements($class) as $p) { + $parents[] = strtolower($p); + ++$i; + } + $parents[] = '*'; + + $this->classInfo[$class] = array($i, $parents, $hasDebugInfo); + } + + $a = Caster::castObject($obj, $class, $hasDebugInfo); + + try { + while ($i--) { + if (!empty($this->casters[$p = $parents[$i]])) { + foreach ($this->casters[$p] as $callback) { + $a = $callback($obj, $a, $stub, $isNested, $this->filter); + } + } + } + } catch (\Exception $e) { + $a = array((Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)) + $a; + } + + return $a; + } + + /** + * Casts a resource to an array representation. + * + * @param Stub $stub The Stub for the casted resource + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array The resource casted as array + */ + protected function castResource(Stub $stub, $isNested) + { + $a = array(); + $res = $stub->value; + $type = $stub->class; + + try { + if (!empty($this->casters[':'.$type])) { + foreach ($this->casters[':'.$type] as $callback) { + $a = $callback($res, $a, $stub, $isNested, $this->filter); + } + } + } catch (\Exception $e) { + $a = array((Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)) + $a; + } + + return $a; + } +}