From 3c401af023f2b4ac0e5639c734dc2123a6eccbf3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 10 Oct 2014 17:31:59 +0200 Subject: [PATCH] [VarDumper] enhance dumping refs --- .../Tests/Extension/DumpExtensionTest.php | 12 +- .../DataCollector/DumpDataCollectorTest.php | 3 +- .../Component/VarDumper/Caster/CasterStub.php | 1 + .../Component/VarDumper/Caster/StubCaster.php | 1 + .../Component/VarDumper/Cloner/Cursor.php | 10 +- .../Component/VarDumper/Cloner/Data.php | 58 ++-- .../VarDumper/Cloner/DumperInterface.php | 60 +--- .../Component/VarDumper/Cloner/Stub.php | 3 +- .../Component/VarDumper/Cloner/VarCloner.php | 129 +++++--- .../Component/VarDumper/Dumper/CliDumper.php | 121 +++----- .../Component/VarDumper/Dumper/HtmlDumper.php | 279 +++++++++++------- .../VarDumper/Tests/CliDumperTest.php | 26 +- .../VarDumper/Tests/HtmlDumperTest.php | 44 +-- 13 files changed, 380 insertions(+), 367 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php index 05166bd29335..acf580578ae8 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php @@ -86,20 +86,20 @@ public function getDumpArgs() { return array( array(array(), array(), '', false), - array(array(), array(), "
[]\n
\n"), + array(array(), array(), "
[]\n
\n"), array( array(), array(123, 456), - "
123\n
\n" - ."
456\n
\n", + "
123\n
\n" + ."
456\n
\n", ), array( array('foo' => 'bar'), array(), - "
array:1 [\n"
+                "
array:1 [\n"
                 ."  \"foo\" => \"bar\"\n"
-                ."]\n"
-                ."
\n", + ."]\n" + ."
\n", ), ); } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php index 5af2959c892e..f18a7f63788a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -35,10 +35,11 @@ public function testDump() $dump = $collector->getDumps('html'); $this->assertTrue(isset($dump[0]['data'])); $dump[0]['data'] = preg_replace('/^.*?
 "
123\n
\n", + 'data' => "
123\n
\n", 'name' => 'DumpDataCollectorTest.php', 'file' => __FILE__, 'line' => $line, diff --git a/src/Symfony/Component/VarDumper/Caster/CasterStub.php b/src/Symfony/Component/VarDumper/Caster/CasterStub.php index 56fc2b112eee..c486dc1aaa4e 100644 --- a/src/Symfony/Component/VarDumper/Caster/CasterStub.php +++ b/src/Symfony/Component/VarDumper/Caster/CasterStub.php @@ -41,6 +41,7 @@ public function __construct($value, $class = '') case 'resource': case 'unknown type': $this->type = self::TYPE_RESOURCE; + $this->handle = (int) $value; $this->class = @get_resource_type($value); $this->cut = -1; break; diff --git a/src/Symfony/Component/VarDumper/Caster/StubCaster.php b/src/Symfony/Component/VarDumper/Caster/StubCaster.php index 294a13c562c2..ba994591744c 100644 --- a/src/Symfony/Component/VarDumper/Caster/StubCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/StubCaster.php @@ -26,6 +26,7 @@ public static function castStub(CasterStub $c, array $a, Stub $stub, $isNested) $stub->type = $c->type; $stub->class = $c->class; $stub->value = $c->value; + $stub->handle = $c->handle; $stub->cut = $c->cut; return array(); diff --git a/src/Symfony/Component/VarDumper/Cloner/Cursor.php b/src/Symfony/Component/VarDumper/Cloner/Cursor.php index 50266ea52ab3..4622b964f27c 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Cursor.php +++ b/src/Symfony/Component/VarDumper/Cloner/Cursor.php @@ -24,9 +24,13 @@ class Cursor const HASH_RESOURCE = Stub::TYPE_RESOURCE; public $depth = 0; - public $refIndex = false; - public $softRefTo = false; - public $hardRefTo = false; + public $refIndex = 0; + public $softRefTo = 0; + public $softRefCount = 0; + public $softRefHandle = 0; + public $hardRefTo = 0; + public $hardRefCount = 0; + public $hardRefHandle = 0; public $hashType; public $hashKey; public $hashIndex = 0; diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 077e19b4875a..fb46aeab19af 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -59,7 +59,7 @@ public function getLimitedClone($maxDepth, $maxItemsPerDepth) public function dump(DumperInterface $dumper) { $refs = array(0); - $this->dumpItem($dumper, new Cursor, $refs, $this->data[0][0]); + $this->dumpItem($dumper, new Cursor(), $refs, $this->data[0][0]); } /** @@ -72,40 +72,41 @@ public function dump(DumperInterface $dumper) */ private function dumpItem($dumper, $cursor, &$refs, $item) { - $cursor->refIndex = $cursor->softRefTo = $cursor->hardRefTo = false; + $cursor->refIndex = 0; + $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; + $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; + $firstSeen = true; if (!$item instanceof Stub) { $type = gettype($item); } elseif (Stub::TYPE_REF === $item->type) { - if ($item->ref) { - if (isset($refs[$r = $item->ref])) { - $cursor->hardRefTo = $refs[$r]; + if ($item->handle) { + if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; } else { - $cursor->refIndex = $refs[$r] = ++$refs[0]; + $firstSeen = false; } + $cursor->hardRefTo = $refs[$r]; + $cursor->hardRefHandle = $item->handle; + $cursor->hardRefCount = $item->refCount; } $type = $item->class ?: gettype($item->value); $item = $item->value; } if ($item instanceof Stub) { - if ($item->ref) { - if (isset($refs[$r = $item->ref])) { - if (Stub::TYPE_ARRAY === $item->type) { - if (false === $cursor->hardRefTo) { - $cursor->hardRefTo = $refs[$r]; - } - } elseif (false === $cursor->softRefTo) { - $cursor->softRefTo = $refs[$r]; - } - } elseif (false !== $cursor->refIndex) { - $refs[$r] = $cursor->refIndex; + if ($item->refCount) { + if (!isset($refs[$r = $item->handle])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; } else { - $cursor->refIndex = $refs[$r] = ++$refs[0]; + $firstSeen = false; } + $cursor->softRefTo = $refs[$r]; } + $cursor->softRefHandle = $item->handle; + $cursor->softRefCount = $item->refCount; $cut = $item->cut; - if ($item->position && false === $cursor->softRefTo && false === $cursor->hardRefTo) { + if ($item->position && $firstSeen) { $children = $this->data[$item->position]; if ($cursor->stop) { @@ -123,29 +124,24 @@ private function dumpItem($dumper, $cursor, &$refs, $item) break; case Stub::TYPE_ARRAY: - $dumper->enterArray($cursor, $item->value, Stub::ARRAY_INDEXED === $item->class, (bool) $children); + $dumper->enterHash($cursor, $item->class, $item->value, (bool) $children); $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->class); - $dumper->leaveArray($cursor, $item->value, Stub::ARRAY_INDEXED === $item->class, (bool) $children, $cut); + $dumper->leaveHash($cursor, $item->class, $item->value, (bool) $children, $cut); break; case Stub::TYPE_OBJECT: - $dumper->enterObject($cursor, $item->class, (bool) $children); - $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, Cursor::HASH_OBJECT); - $dumper->leaveObject($cursor, $item->class, (bool) $children, $cut); - break; - case Stub::TYPE_RESOURCE: - $dumper->enterResource($cursor, $item->class, (bool) $children); - $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, Cursor::HASH_RESOURCE); - $dumper->leaveResource($cursor, $item->class, (bool) $children, $cut); + $dumper->enterHash($cursor, $item->type, $item->class, (bool) $children); + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type); + $dumper->leaveHash($cursor, $item->type, $item->class, (bool) $children, $cut); break; default: throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type)); } } elseif ('array' === $type) { - $dumper->enterArray($cursor, 0, true, 0, 0); - $dumper->leaveArray($cursor, 0, true, 0, 0); + $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); + $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); } else { $dumper->dumpScalar($cursor, $type, $item); } diff --git a/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php index d910834ddf96..eba23d42297e 100644 --- a/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php +++ b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php @@ -38,61 +38,23 @@ public function dumpScalar(Cursor $cursor, $type, $value); public function dumpString(Cursor $cursor, $str, $bin, $cut); /** - * Dumps while entering an array. + * Dumps while entering an hash. * * @param Cursor $cursor The Cursor position in the dump. - * @param int $count The number of items in the original array. - * @param bool $indexed When the array is indexed or associative. - * @param bool $hasChild When the dump of the array has child item. + * @param int $type A Cursor::HASH_* const for the type of hash. + * @param string $class The object class, resource type or array count. + * @param bool $hasChild When the dump of the hash has child item. */ - public function enterArray(Cursor $cursor, $count, $indexed, $hasChild); + public function enterHash(Cursor $cursor, $type, $class, $hasChild); /** - * Dumps while leaving an array. + * Dumps while leaving an hash. * * @param Cursor $cursor The Cursor position in the dump. - * @param int $count The number of items in the original array. - * @param bool $indexed Whether the array is indexed or associative. - * @param bool $hasChild When the dump of the array has child item. - * @param int $cut The number of items the array has been cut by. + * @param int $type A Cursor::HASH_* const for the type of hash. + * @param string $class The object class, resource type or array count. + * @param bool $hasChild When the dump of the hash has child item. + * @param int $cut The number of items the hash has been cut by. */ - public function leaveArray(Cursor $cursor, $count, $indexed, $hasChild, $cut); - - /** - * Dumps while entering an object. - * - * @param Cursor $cursor The Cursor position in the dump. - * @param string $class The class of the object. - * @param bool $hasChild When the dump of the object has child item. - */ - public function enterObject(Cursor $cursor, $class, $hasChild); - - /** - * Dumps while leaving an object. - * - * @param Cursor $cursor The Cursor position in the dump. - * @param string $class The class of the object. - * @param bool $hasChild When the dump of the object has child item. - * @param int $cut The number of items the object has been cut by. - */ - public function leaveObject(Cursor $cursor, $class, $hasChild, $cut); - - /** - * Dumps while entering a resource. - * - * @param Cursor $cursor The Cursor position in the dump. - * @param string $res The resource type. - * @param bool $hasChild When the dump of the resource has child item. - */ - public function enterResource(Cursor $cursor, $res, $hasChild); - - /** - * Dumps while leaving a resource. - * - * @param Cursor $cursor The Cursor position in the dump. - * @param string $res The resource type. - * @param bool $hasChild When the dump of the resource has child item. - * @param int $cut The number of items the resource has been cut by. - */ - public function leaveResource(Cursor $cursor, $res, $hasChild, $cut); + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut); } diff --git a/src/Symfony/Component/VarDumper/Cloner/Stub.php b/src/Symfony/Component/VarDumper/Cloner/Stub.php index 9400a1e077d1..f58a57a72761 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Stub.php +++ b/src/Symfony/Component/VarDumper/Cloner/Stub.php @@ -34,6 +34,7 @@ class Stub public $class = ''; public $value; public $cut = 0; - public $ref = 0; + public $handle = 0; + public $refCount = 0; public $position = 0; } diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php index 6d1a9ffd9531..605977be8089 100644 --- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php @@ -16,6 +16,9 @@ */ class VarCloner extends AbstractCloner { + private static $hashMask = 0; + private static $hashOffset = 0; + /** * {@inheritdoc} */ @@ -25,11 +28,12 @@ protected function doClone($var) $i = 0; // Current iteration position in $queue $len = 1; // Length of $queue $pos = 0; // Number of cloned items past the first level - $refs = 0; // Number of hard+soft references in $var + $refs = 0; // Hard references counter $queue = array(array($var)); // This breadth-first queue is the return value $arrayRefs = array(); // Map of queue indexes to stub array objects $hardRefs = array(); // Map of original zval hashes to stub objects - $softRefs = array(); // Map of original object hashes to their stub object couterpart + $objRefs = array(); // Map of original object handles to their stub object couterpart + $resRefs = array(); // Map of original resource handles to their stub object couterpart $values = array(); // Map of stub objects' hashes to original values $maxItems = $this->maxItems; $maxString = $this->maxString; @@ -40,11 +44,17 @@ protected function doClone($var) $zval = array( // Main properties of the current value 'type' => null, 'zval_isref' => null, + 'zval_hash' => null, 'array_count' => null, 'object_class' => null, - 'object_hash' => null, + 'object_handle' => null, 'resource_type' => null, ); + if (!self::$hashMask) { + self::initHashMask(); + } + $hashMask = self::$hashMask; + $hashOffset = self::$hashOffset; for ($i = 0; $i < $len; ++$i) { $indexed = true; // Whether the currently iterated array is numerically indexed or not @@ -58,28 +68,25 @@ protected function doClone($var) } if ($useExt) { $zval = symfony_zval_info($k, $step); - if ($zval['zval_isref']) { - $queue[$i][$k] =& $stub; // Break hard references to make $queue completely - unset($stub); // independent from the original structure - if (isset($hardRefs[$h = $zval['zval_hash']])) { - $hardRefs[$h]->ref = ++$refs; - $queue[$i][$k] = $hardRefs[$h]; - continue; - } - } } else { $step[$k] = $cookie; if ($zval['zval_isref'] = $queue[$i][$k] === $cookie) { - $queue[$i][$k] =& $stub; // Break hard references to make $queue completely - unset($stub); // independent from the original structure - if ($v instanceof Stub && isset($hardRefs[spl_object_hash($v)])) { - $v->ref = ++$refs; - $step[$k] = $queue[$i][$k] = $v; - continue; - } + $zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null; } $zval['type'] = gettype($v); } + if ($zval['zval_isref']) { + $queue[$i][$k] =& $stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if (isset($hardRefs[$zval['zval_hash']])) { + $queue[$i][$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($step[$k] = $v); + if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { + ++$v->value->refCount; + } + ++$v->refCount; + continue; + } + } // Create $stub when the original value $v can not be used directly // If $v is a nested structure, put that structure in array $a switch ($zval['type']) { @@ -112,23 +119,31 @@ protected function doClone($var) $stub = $arrayRefs[$len] = new Stub(); $stub->type = Stub::TYPE_ARRAY; $stub->class = Stub::ARRAY_ASSOC; - $stub->value = $v ? $zval['array_count'] ?: count($v) : 0; + $stub->value = $zval['array_count'] ?: count($v); $a = $v; } break; case 'object': - if (empty($softRefs[$h = $zval['object_hash'] ?: spl_object_hash($v)])) { + if (empty($objRefs[$h = $zval['object_handle'] ?: ($hashMask ^ hexdec(substr(spl_object_hash($v), $hashOffset, PHP_INT_SIZE)))])) { $stub = new Stub(); $stub->type = Stub::TYPE_OBJECT; $stub->class = $zval['object_class'] ?: get_class($v); $stub->value = $v; + $stub->handle = $h; $a = $this->castObject($stub, 0 < $i); if ($v !== $stub->value) { if (Stub::TYPE_OBJECT !== $stub->type) { break; } - $h = spl_object_hash($stub->value); + if ($useExt) { + $zval['type'] = $stub->value; + $zval = symfony_zval_info('type', $zval); + $h = $zval['object_handle']; + } else { + $h = $hashMask ^ hexdec(substr(spl_object_hash($stub->value), $hashOffset, PHP_INT_SIZE)); + } + $stub->handle = $h; } $stub->value = null; if (0 <= $maxItems && $maxItems <= $pos) { @@ -136,40 +151,35 @@ protected function doClone($var) $a = null; } } - if (empty($softRefs[$h])) { - $softRefs[$h] = $stub; + if (empty($objRefs[$h])) { + $objRefs[$h] = $stub; } else { - $stub = $softRefs[$h]; - $stub->ref = ++$refs; + $stub = $objRefs[$h]; + ++$stub->refCount; $a = null; } break; case 'resource': case 'unknown type': - if (empty($softRefs[$h = (int) $v])) { + if (empty($resRefs[$h = (int) $v])) { $stub = new Stub(); $stub->type = Stub::TYPE_RESOURCE; $stub->class = $zval['resource_type'] ?: get_resource_type($v); $stub->value = $v; + $stub->handle = $h; $a = $this->castResource($stub, 0 < $i); - if ($v !== $stub->value) { - if (Stub::TYPE_RESOURCE !== $stub->type) { - break; - } - $h = (int) $stub->value; - } $stub->value = null; if (0 <= $maxItems && $maxItems <= $pos) { $stub->cut = count($a); $a = null; } } - if (empty($softRefs[$h])) { - $softRefs[$h] = $stub; + if (empty($resRefs[$h])) { + $resRefs[$h] = $stub; } else { - $stub = $softRefs[$h]; - $stub->ref = ++$refs; + $stub = $resRefs[$h]; + ++$stub->refCount; $a = null; } break; @@ -178,23 +188,16 @@ protected function doClone($var) if (isset($stub)) { if ($zval['zval_isref']) { if ($useExt) { - if (Stub::TYPE_ARRAY === $stub->type) { - $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $stub; - } else { - $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub(); - $v->value = $stub; - } + $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub(); + $v->value = $stub; } else { - if (Stub::TYPE_ARRAY === $stub->type) { - $step[$k] = $stub; - } else { - $step[$k] = new Stub(); - $step[$k]->value = $stub; - } + $step[$k] = new Stub(); + $step[$k]->value = $stub; $h = spl_object_hash($step[$k]); $queue[$i][$k] = $hardRefs[$h] =& $step[$k]; $values[$h] = $v; } + $queue[$i][$k]->handle = ++$refs; } else { $queue[$i][$k] = $stub; } @@ -233,6 +236,7 @@ protected function doClone($var) $hardRefs[$h] =& $step[$k]; $values[$h] = $v; } + $queue[$i][$k]->handle = ++$refs; } } @@ -250,4 +254,31 @@ protected function doClone($var) return $queue; } + + private static function initHashMask() + { + $obj = (object) array(); + self::$hashOffset = 16 - PHP_INT_SIZE; + self::$hashMask = -1; + + if (defined('HHVM_VERSION')) { + self::$hashOffset += 16; + } else { + // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below + $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'); + foreach (debug_backtrace(PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && in_array($frame['function'], $obFuncs)) { + $frame['line'] = 0; + break; + } + } + if (!empty($frame['line'])) { + ob_start(); + debug_zval_dump($obj); + self::$hashMask = substr(ob_get_clean(), 17); + } + } + + self::$hashMask ^= hexdec(substr(spl_object_hash($obj), self::$hashOffset, PHP_INT_SIZE)); + } } diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index 483faad1b4ad..b3344a237abb 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -33,7 +33,8 @@ class CliDumper extends AbstractDumper 'str' => '1;38;5;37', 'cchr' => '7', 'note' => '38;5;178', - 'ref' => '38;5;245', + 'ref' => '38;5;240', + 'solo-ref' => '38;5;240', 'public' => '38;5;28', 'protected' => '38;5;166', 'private' => '38;5;160', @@ -125,7 +126,7 @@ public function dumpScalar(Cursor $cursor, $type, $val) $this->line .= $this->style($style, $val); - $this->endLine($cursor); + $this->dumpLine($cursor->depth); } /** @@ -137,7 +138,7 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) if ('' === $str) { $this->line .= '""'; - $this->endLine($cursor); + $this->dumpLine($cursor->depth); } else { $str = explode("\n", $str); $m = count($str) - 1; @@ -149,7 +150,7 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) if ($m) { $this->line .= '"""'; - $this->endLine($cursor); + $this->dumpLine($cursor->depth); } else { $this->line .= '"'; } @@ -182,7 +183,7 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) $lineCut = 0; } - $this->endLine($cursor, !$m); + $this->dumpLine($cursor->depth); } } } @@ -190,93 +191,63 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) /** * {@inheritdoc} */ - public function enterArray(Cursor $cursor, $count, $indexed, $hasChild) + public function enterHash(Cursor $cursor, $type, $class, $hasChild) { - $this->enterHash($cursor, $count ? $this->style('note', 'array:'.$count).' [' : '[', $hasChild); - } + $this->dumpKey($cursor); - /** - * {@inheritdoc} - */ - public function leaveArray(Cursor $cursor, $count, $indexed, $hasChild, $cut) - { - $this->leaveHash($cursor, ']', $hasChild, $cut); - } + if (Cursor::HASH_OBJECT === $type) { + $prefix = 'stdClass' !== $class ? $this->style('note', $class).' {' : '{'; + } elseif (Cursor::HASH_RESOURCE === $type) { + $prefix = $this->style('note', ':'.$class).' {'; + } else { + $prefix = $class ? $this->style('note', 'array:'.$class).' [' : '['; + } - /** - * {@inheritdoc} - */ - public function enterObject(Cursor $cursor, $class, $hasChild) - { - $this->enterHash($cursor, 'stdClass' !== $class ? $this->style('note', $class).' {' : '{', $hasChild); - } + if (Cursor::HASH_RESOURCE === $type) { + $prefix .= $this->style('ref', '@'.$cursor->softRefHandle); + } elseif ($cursor->softRefTo) { + $prefix .= $this->style('ref', '#'.(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo)); + } elseif (0 < $cursor->softRefHandle) { + $prefix .= $this->style('solo-ref', '#'.$cursor->softRefHandle); + } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { + $prefix .= $this->style('ref', '&'.$cursor->hardRefTo); + } - /** - * {@inheritdoc} - */ - public function leaveObject(Cursor $cursor, $class, $hasChild, $cut) - { - $this->leaveHash($cursor, '}', $hasChild, $cut); - } + $this->line .= $prefix; - /** - * {@inheritdoc} - */ - public function enterResource(Cursor $cursor, $res, $hasChild) - { - $this->enterHash($cursor, $this->style('note', ':'.$res).' {', $hasChild); + if ($hasChild) { + $this->dumpLine($cursor->depth); + } } /** * {@inheritdoc} */ - public function leaveResource(Cursor $cursor, $res, $hasChild, $cut) - { - $this->leaveHash($cursor, '}', $hasChild, $cut); - } - - /** - * Generic dumper used while entering any hash-style structure. - * - * @param Cursor $cursor The Cursor position in the dump. - * @param string $prefix The string that starts the next dumped line. - * @param bool $hasChild When the dump of the hash has child item. - */ - protected function enterHash(Cursor $cursor, $prefix, $hasChild) + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) { - $this->dumpKey($cursor); - - $this->line .= $prefix; - if (false !== $cursor->softRefTo) { - $this->line .= $this->style('ref', '@'.$cursor->softRefTo); - } elseif (false !== $cursor->hardRefTo) { - $this->line .= $this->style('ref', '@'.$cursor->hardRefTo); - } elseif ($hasChild) { - $this->endLine($cursor); - } + $this->dumpEllipsis($cursor, $hasChild, $cut); + $this->line .= Cursor::HASH_OBJECT === $type || Cursor::HASH_RESOURCE === $type ? '}' : ']'; + $this->dumpLine($cursor->depth); } /** - * Generic dumper used while leaving any hash-style structure. + * Dumps an ellipsis for cut children. * * @param Cursor $cursor The Cursor position in the dump. - * @param string $suffix The string that ends the next dumped line. * @param bool $hasChild When the dump of the hash has child item. * @param int $cut The number of items the hash has been cut by. */ - protected function leaveHash(Cursor $cursor, $suffix, $hasChild, $cut) + protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) { - if ($cut && false === $cursor->softRefTo && false === $cursor->hardRefTo) { - $this->line .= '…'; + if ($cut) { + $this->line .= ' …'; if (0 < $cut) { $this->line .= $cut; } if ($hasChild) { - $this->dumpLine($cursor->depth+1); + $this->dumpLine($cursor->depth + 1); } } - $this->line .= $suffix; - $this->endLine($cursor, !$hasChild); } /** @@ -329,26 +300,12 @@ protected function dumpKey(Cursor $cursor) break; } - if (false !== $cursor->hardRefTo) { - $this->line .= $this->style('ref', '&'.$cursor->hardRefTo).' '; + if ($cursor->hardRefTo) { + $this->line .= ($cursor->hardRefCount ? $this->style('ref', '&'.$cursor->hardRefTo) : $this->style('solo-ref', '&')).' '; } } } - /** - * Finishes a line and dumps it. - * - * @param Cursor $cursor The current Cursor position. - * @param bool $showRef Show/hide the current ref index. - */ - protected function endLine(Cursor $cursor, $showRef = true) - { - if ($showRef && false !== $cursor->refIndex) { - $this->line .= ' '.$this->style('ref', '#'.$cursor->refIndex); - } - $this->dumpLine($cursor->depth); - } - /** * Decorates a value with some style. * diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index b6093fa14ed2..025545fa2e82 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -24,8 +24,8 @@ class HtmlDumper extends CliDumper public static $defaultOutputStream = 'php://output'; protected $dumpHeader; - protected $dumpPrefix = '
';
-    protected $dumpSuffix = '
'; + protected $dumpPrefix = '
';
+    protected $dumpSuffix = '
'; protected $dumpId = 'sf-dump'; protected $colors = true; protected $headerIsDumped = false; @@ -36,7 +36,8 @@ class HtmlDumper extends CliDumper 'str' => 'font-weight:bold;color:#00D7FF', 'cchr' => 'font-style: italic', 'note' => 'color:#D7AF00', - 'ref' => 'color:#444444', + 'ref' => 'color:#585858', + 'solo-ref' => 'color:#585858', 'public' => 'color:#008700', 'protected' => 'color:#D75F00', 'private' => 'color:#D70000', @@ -106,99 +107,140 @@ protected function getDumpHeader() $line = <<<'EOHTML'