[VarDumper] Added setMinDepth to VarCloner

This new function allows VarCloner users to specify a minimum tree
depth that must be fully explored before we start limiting the number of
cloned items via the existing setMaxItems functionality.

It’s useful for dumping arguments from a backtrace to ensure some
minimum level of detail, while keeping a very low setMaxItems value to
ensure fast performance.
This commit is contained in:
James Johnston 2017-07-14 23:43:02 -07:00
parent de5c60de0e
commit d6534f5cfc
4 changed files with 290 additions and 5 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
3.4.0
-----
* added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth
2.7.0
-----

View File

@ -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.
*

View File

@ -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) {

View File

@ -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 = <<<EOTXT
Symfony\Component\VarDumper\Cloner\Data Object
(
[data:Symfony\Component\VarDumper\Cloner\Data:private] => 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));
}