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 = '
+ */ +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; + } +}