diff --git a/src/Symfony/Component/VarDumper/CHANGELOG.md b/src/Symfony/Component/VarDumper/CHANGELOG.md index 6b08aa77ac..24a5843f64 100644 --- a/src/Symfony/Component/VarDumper/CHANGELOG.md +++ b/src/Symfony/Component/VarDumper/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +3.4.0 +----- + + * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth + 2.7.0 ----- diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index d77745e931..bd98dd3185 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -129,6 +129,7 @@ abstract class AbstractCloner implements ClonerInterface protected $maxItems = 2500; protected $maxString = -1; + protected $minDepth = 1; protected $useExt; private $casters = array(); @@ -168,7 +169,7 @@ abstract class AbstractCloner implements ClonerInterface } /** - * Sets the maximum number of items to clone past the first level in nested structures. + * Sets the maximum number of items to clone past the minimum depth in nested structures. * * @param int $maxItems */ @@ -187,6 +188,17 @@ abstract class AbstractCloner implements ClonerInterface $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. * diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php index 6a3b451bda..f1cccd309b 100644 --- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php @@ -26,7 +26,7 @@ class VarCloner extends AbstractCloner { $useExt = $this->useExt; $len = 1; // Length of $queue - $pos = 0; // Number of cloned items past the first level + $pos = 0; // Number of cloned items past the minimum depth $refsCounter = 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 @@ -36,6 +36,10 @@ class VarCloner extends AbstractCloner $values = array(); // Map of stub objects' hashes to original values $maxItems = $this->maxItems; $maxString = $this->maxString; + $minDepth = $this->minDepth; + $currentDepth = 0; // Current tree depth + $currentDepthFinalIndex = 0; // Final $queue index for current tree depth + $minimumDepthReached = $minDepth === 0; // Becomes true when minimum tree depth has been reached $cookie = (object) array(); // Unique object used to detect hard references $gid = uniqid(mt_rand(), true); // Unique string used to detect the special $GLOBALS variable $a = null; // Array cast for nested structures @@ -57,6 +61,15 @@ class VarCloner extends AbstractCloner $hashOffset = self::$hashOffset; for ($i = 0; $i < $len; ++$i) { + // Detect when we move on to the next tree depth + if ($i > $currentDepthFinalIndex) { + ++$currentDepth; + $currentDepthFinalIndex = $len - 1; + if ($currentDepth >= $minDepth) { + $minimumDepthReached = true; + } + } + $indexed = true; // Whether the currently iterated array is numerically indexed or not $j = -1; // Position in the currently iterated array $fromObjCast = array_keys($queue[$i]); @@ -166,7 +179,7 @@ class VarCloner extends AbstractCloner $stub->handle = $h; } $stub->value = null; - if (0 <= $maxItems && $maxItems <= $pos) { + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { $stub->cut = count($a); $a = null; } @@ -193,7 +206,7 @@ class VarCloner extends AbstractCloner $stub->handle = $h; $a = $this->castResource($stub, 0 < $i); $stub->value = null; - if (0 <= $maxItems && $maxItems <= $pos) { + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { $stub->cut = count($a); $a = null; } @@ -226,7 +239,7 @@ class VarCloner extends AbstractCloner } if ($a) { - if ($i && 0 <= $maxItems) { + if ($minimumDepthReached && 0 <= $maxItems) { $k = count($a); if ($pos < $maxItems) { if ($maxItems < $pos += $k) { diff --git a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php index 75774170f3..458a2906ec 100644 --- a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php @@ -152,6 +152,261 @@ Symfony\Component\VarDumper\Cloner\Data Object [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 ) +EOTXT; + $this->assertStringMatchesFormat($expected, print_r($clone, true)); + } + + public function testLimits() + { + // Level 0: + $data = array( + // Level 1: + array( + // Level 2: + array( + // Level 3: + 'Level 3 Item 0', + 'Level 3 Item 1', + 'Level 3 Item 2', + 'Level 3 Item 3', + ), + array( + 'Level 3 Item 4', + 'Level 3 Item 5', + 'Level 3 Item 6', + ), + array( + 'Level 3 Item 7', + ), + ), + array( + array( + 'Level 3 Item 8', + ), + 'Level 2 Item 0', + ), + array( + 'Level 2 Item 1', + ), + 'Level 1 Item 0', + array( + // Test setMaxString: + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'SHORT', + ), + ); + + $cloner = new VarCloner(); + $cloner->setMinDepth(2); + $cloner->setMaxItems(5); + $cloner->setMaxString(20); + $clone = $cloner->cloneVar($data); + + $expected = << Array + ( + [0] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => indexed + [value] => 5 + [cut] => 0 + [handle] => 0 + [refCount] => 0 + [position] => 1 + [attr] => Array + ( + ) + + ) + + ) + + [1] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => indexed + [value] => 3 + [cut] => 0 + [handle] => 0 + [refCount] => 0 + [position] => 2 + [attr] => Array + ( + ) + + ) + + [1] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => indexed + [value] => 2 + [cut] => 0 + [handle] => 0 + [refCount] => 0 + [position] => 3 + [attr] => Array + ( + ) + + ) + + [2] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => indexed + [value] => 1 + [cut] => 0 + [handle] => 0 + [refCount] => 0 + [position] => 4 + [attr] => Array + ( + ) + + ) + + [3] => Level 1 Item 0 + [4] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => indexed + [value] => 2 + [cut] => 0 + [handle] => 0 + [refCount] => 0 + [position] => 5 + [attr] => Array + ( + ) + + ) + + ) + + [2] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => indexed + [value] => 4 + [cut] => 0 + [handle] => 0 + [refCount] => 0 + [position] => 6 + [attr] => Array + ( + ) + + ) + + [1] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => indexed + [value] => 3 + [cut] => 2 + [handle] => 0 + [refCount] => 0 + [position] => 7 + [attr] => Array + ( + ) + + ) + + [2] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => assoc + [value] => 1 + [cut] => 1 + [handle] => 0 + [refCount] => 0 + [position] => 0 + [attr] => Array + ( + ) + + ) + + ) + + [3] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => array + [class] => assoc + [value] => 1 + [cut] => 1 + [handle] => 0 + [refCount] => 0 + [position] => 0 + [attr] => Array + ( + ) + + ) + + [1] => Level 2 Item 0 + ) + + [4] => Array + ( + [0] => Level 2 Item 1 + ) + + [5] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => string + [class] => utf8 + [value] => ABCDEFGHIJKLMNOPQRST + [cut] => 6 + [handle] => 0 + [refCount] => 0 + [position] => 0 + [attr] => Array + ( + ) + + ) + + [1] => SHORT + ) + + [6] => Array + ( + [0] => Level 3 Item 0 + [1] => Level 3 Item 1 + [2] => Level 3 Item 2 + [3] => Level 3 Item 3 + ) + + [7] => Array + ( + [0] => Level 3 Item 4 + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 +) + EOTXT; $this->assertStringMatchesFormat($expected, print_r($clone, true)); }