Skip to content

Commit d6534f5

Browse files
[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.
1 parent de5c60d commit d6534f5

File tree

4 files changed

+290
-5
lines changed

4 files changed

+290
-5
lines changed

src/Symfony/Component/VarDumper/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
3.4.0
5+
-----
6+
7+
* added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth
8+
49
2.7.0
510
-----
611

src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ abstract class AbstractCloner implements ClonerInterface
129129

130130
protected $maxItems = 2500;
131131
protected $maxString = -1;
132+
protected $minDepth = 1;
132133
protected $useExt;
133134

134135
private $casters = array();
@@ -168,7 +169,7 @@ public function addCasters(array $casters)
168169
}
169170

170171
/**
171-
* Sets the maximum number of items to clone past the first level in nested structures.
172+
* Sets the maximum number of items to clone past the minimum depth in nested structures.
172173
*
173174
* @param int $maxItems
174175
*/
@@ -187,6 +188,17 @@ public function setMaxString($maxString)
187188
$this->maxString = (int) $maxString;
188189
}
189190

191+
/**
192+
* Sets the minimum tree depth where we are guaranteed to clone all the items. After this
193+
* depth is reached, only setMaxItems items will be cloned.
194+
*
195+
* @param int $minDepth
196+
*/
197+
public function setMinDepth($minDepth)
198+
{
199+
$this->minDepth = (int) $minDepth;
200+
}
201+
190202
/**
191203
* Clones a PHP variable.
192204
*

src/Symfony/Component/VarDumper/Cloner/VarCloner.php

+17-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protected function doClone($var)
2626
{
2727
$useExt = $this->useExt;
2828
$len = 1; // Length of $queue
29-
$pos = 0; // Number of cloned items past the first level
29+
$pos = 0; // Number of cloned items past the minimum depth
3030
$refsCounter = 0; // Hard references counter
3131
$queue = array(array($var)); // This breadth-first queue is the return value
3232
$arrayRefs = array(); // Map of queue indexes to stub array objects
@@ -36,6 +36,10 @@ protected function doClone($var)
3636
$values = array(); // Map of stub objects' hashes to original values
3737
$maxItems = $this->maxItems;
3838
$maxString = $this->maxString;
39+
$minDepth = $this->minDepth;
40+
$currentDepth = 0; // Current tree depth
41+
$currentDepthFinalIndex = 0; // Final $queue index for current tree depth
42+
$minimumDepthReached = $minDepth === 0; // Becomes true when minimum tree depth has been reached
3943
$cookie = (object) array(); // Unique object used to detect hard references
4044
$gid = uniqid(mt_rand(), true); // Unique string used to detect the special $GLOBALS variable
4145
$a = null; // Array cast for nested structures
@@ -57,6 +61,15 @@ protected function doClone($var)
5761
$hashOffset = self::$hashOffset;
5862

5963
for ($i = 0; $i < $len; ++$i) {
64+
// Detect when we move on to the next tree depth
65+
if ($i > $currentDepthFinalIndex) {
66+
++$currentDepth;
67+
$currentDepthFinalIndex = $len - 1;
68+
if ($currentDepth >= $minDepth) {
69+
$minimumDepthReached = true;
70+
}
71+
}
72+
6073
$indexed = true; // Whether the currently iterated array is numerically indexed or not
6174
$j = -1; // Position in the currently iterated array
6275
$fromObjCast = array_keys($queue[$i]);
@@ -166,7 +179,7 @@ protected function doClone($var)
166179
$stub->handle = $h;
167180
}
168181
$stub->value = null;
169-
if (0 <= $maxItems && $maxItems <= $pos) {
182+
if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
170183
$stub->cut = count($a);
171184
$a = null;
172185
}
@@ -193,7 +206,7 @@ protected function doClone($var)
193206
$stub->handle = $h;
194207
$a = $this->castResource($stub, 0 < $i);
195208
$stub->value = null;
196-
if (0 <= $maxItems && $maxItems <= $pos) {
209+
if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
197210
$stub->cut = count($a);
198211
$a = null;
199212
}
@@ -226,7 +239,7 @@ protected function doClone($var)
226239
}
227240

228241
if ($a) {
229-
if ($i && 0 <= $maxItems) {
242+
if ($minimumDepthReached && 0 <= $maxItems) {
230243
$k = count($a);
231244
if ($pos < $maxItems) {
232245
if ($maxItems < $pos += $k) {

src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php

+255
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,261 @@ public function testClone()
152152
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
153153
)
154154
155+
EOTXT;
156+
$this->assertStringMatchesFormat($expected, print_r($clone, true));
157+
}
158+
159+
public function testLimits()
160+
{
161+
// Level 0:
162+
$data = array(
163+
// Level 1:
164+
array(
165+
// Level 2:
166+
array(
167+
// Level 3:
168+
'Level 3 Item 0',
169+
'Level 3 Item 1',
170+
'Level 3 Item 2',
171+
'Level 3 Item 3',
172+
),
173+
array(
174+
'Level 3 Item 4',
175+
'Level 3 Item 5',
176+
'Level 3 Item 6',
177+
),
178+
array(
179+
'Level 3 Item 7',
180+
),
181+
),
182+
array(
183+
array(
184+
'Level 3 Item 8',
185+
),
186+
'Level 2 Item 0',
187+
),
188+
array(
189+
'Level 2 Item 1',
190+
),
191+
'Level 1 Item 0',
192+
array(
193+
// Test setMaxString:
194+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
195+
'SHORT',
196+
),
197+
);
198+
199+
$cloner = new VarCloner();
200+
$cloner->setMinDepth(2);
201+
$cloner->setMaxItems(5);
202+
$cloner->setMaxString(20);
203+
$clone = $cloner->cloneVar($data);
204+
205+
$expected = <<<EOTXT
206+
Symfony\Component\VarDumper\Cloner\Data Object
207+
(
208+
[data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
209+
(
210+
[0] => Array
211+
(
212+
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
213+
(
214+
[type] => array
215+
[class] => indexed
216+
[value] => 5
217+
[cut] => 0
218+
[handle] => 0
219+
[refCount] => 0
220+
[position] => 1
221+
[attr] => Array
222+
(
223+
)
224+
225+
)
226+
227+
)
228+
229+
[1] => Array
230+
(
231+
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
232+
(
233+
[type] => array
234+
[class] => indexed
235+
[value] => 3
236+
[cut] => 0
237+
[handle] => 0
238+
[refCount] => 0
239+
[position] => 2
240+
[attr] => Array
241+
(
242+
)
243+
244+
)
245+
246+
[1] => Symfony\Component\VarDumper\Cloner\Stub Object
247+
(
248+
[type] => array
249+
[class] => indexed
250+
[value] => 2
251+
[cut] => 0
252+
[handle] => 0
253+
[refCount] => 0
254+
[position] => 3
255+
[attr] => Array
256+
(
257+
)
258+
259+
)
260+
261+
[2] => Symfony\Component\VarDumper\Cloner\Stub Object
262+
(
263+
[type] => array
264+
[class] => indexed
265+
[value] => 1
266+
[cut] => 0
267+
[handle] => 0
268+
[refCount] => 0
269+
[position] => 4
270+
[attr] => Array
271+
(
272+
)
273+
274+
)
275+
276+
[3] => Level 1 Item 0
277+
[4] => Symfony\Component\VarDumper\Cloner\Stub Object
278+
(
279+
[type] => array
280+
[class] => indexed
281+
[value] => 2
282+
[cut] => 0
283+
[handle] => 0
284+
[refCount] => 0
285+
[position] => 5
286+
[attr] => Array
287+
(
288+
)
289+
290+
)
291+
292+
)
293+
294+
[2] => Array
295+
(
296+
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
297+
(
298+
[type] => array
299+
[class] => indexed
300+
[value] => 4
301+
[cut] => 0
302+
[handle] => 0
303+
[refCount] => 0
304+
[position] => 6
305+
[attr] => Array
306+
(
307+
)
308+
309+
)
310+
311+
[1] => Symfony\Component\VarDumper\Cloner\Stub Object
312+
(
313+
[type] => array
314+
[class] => indexed
315+
[value] => 3
316+
[cut] => 2
317+
[handle] => 0
318+
[refCount] => 0
319+
[position] => 7
320+
[attr] => Array
321+
(
322+
)
323+
324+
)
325+
326+
[2] => Symfony\Component\VarDumper\Cloner\Stub Object
327+
(
328+
[type] => array
329+
[class] => assoc
330+
[value] => 1
331+
[cut] => 1
332+
[handle] => 0
333+
[refCount] => 0
334+
[position] => 0
335+
[attr] => Array
336+
(
337+
)
338+
339+
)
340+
341+
)
342+
343+
[3] => Array
344+
(
345+
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
346+
(
347+
[type] => array
348+
[class] => assoc
349+
[value] => 1
350+
[cut] => 1
351+
[handle] => 0
352+
[refCount] => 0
353+
[position] => 0
354+
[attr] => Array
355+
(
356+
)
357+
358+
)
359+
360+
[1] => Level 2 Item 0
361+
)
362+
363+
[4] => Array
364+
(
365+
[0] => Level 2 Item 1
366+
)
367+
368+
[5] => Array
369+
(
370+
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
371+
(
372+
[type] => string
373+
[class] => utf8
374+
[value] => ABCDEFGHIJKLMNOPQRST
375+
[cut] => 6
376+
[handle] => 0
377+
[refCount] => 0
378+
[position] => 0
379+
[attr] => Array
380+
(
381+
)
382+
383+
)
384+
385+
[1] => SHORT
386+
)
387+
388+
[6] => Array
389+
(
390+
[0] => Level 3 Item 0
391+
[1] => Level 3 Item 1
392+
[2] => Level 3 Item 2
393+
[3] => Level 3 Item 3
394+
)
395+
396+
[7] => Array
397+
(
398+
[0] => Level 3 Item 4
399+
)
400+
401+
)
402+
403+
[position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
404+
[key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
405+
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
406+
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
407+
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
408+
)
409+
155410
EOTXT;
156411
$this->assertStringMatchesFormat($expected, print_r($clone, true));
157412
}

0 commit comments

Comments
 (0)