Skip to content

Commit cae0933

Browse files
committed
[Debug] Added an exception processor for dumping arguments
1 parent 4f299c7 commit cae0933

File tree

4 files changed

+191
-53
lines changed

4 files changed

+191
-53
lines changed

src/Symfony/Bundle/DebugBundle/Resources/config/services.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
<tag name="exception.processor" />
4040
</service>
4141

42+
<service id="debug.exception_processor.arguments" class="Symfony\Component\Debug\ArgumentsFlattenExceptionProcessor" public="false">
43+
<tag name="exception.processor" />
44+
<argument type="service" id="var_dumper.cloner" />
45+
</service>
4246
</services>
4347

4448
</container>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Debug;
13+
14+
use Symfony\Component\Debug\Exception\FlattenException;
15+
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
16+
17+
/**
18+
* @author Martin Hasoň <martin.hason@gmail.com>
19+
*/
20+
class ArgumentsFlattenExceptionProcessor implements FlattenExceptionProcessorInterface
21+
{
22+
private $cloner;
23+
private $link;
24+
25+
public function __construct(ClonerInterface $cloner = null, $link = true)
26+
{
27+
$this->cloner = $cloner;
28+
$this->link = $link;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function process(\Exception $exception, FlattenException $flattenException, $master)
35+
{
36+
if (!$master) {
37+
return;
38+
}
39+
40+
$variables = array();
41+
$values = array();
42+
43+
$e = $exception;
44+
$f = $flattenException;
45+
46+
do {
47+
$trace = $f->getTrace();
48+
foreach ($e->getTrace() as $key => $entry) {
49+
if (!isset($entry['args']) || !isset($trace[$key])) {
50+
continue;
51+
}
52+
53+
$parameters = $this->getParameters($entry);
54+
55+
$arguments = array();
56+
foreach ($entry['args'] as $position => $argument) {
57+
$link = array_search($argument, $variables, true);
58+
59+
if (false === $link) {
60+
$link = hash('md5', uniqid(mt_rand(), true), false);
61+
$variables[$link] = $argument;
62+
$values[$link] = $this->link ? array('link', $link) : $this->flatten($argument);
63+
}
64+
65+
if (isset($parameters[$position])) {
66+
$arguments[$parameters[$position]->getName()] = $values[$link];
67+
} else {
68+
$arguments[] = $values[$link];
69+
}
70+
}
71+
72+
$trace[$key]['args'] = $arguments;
73+
}
74+
$f->replaceTrace($trace);
75+
} while (($e = $e->getPrevious()) && ($f = $f->getPrevious()));
76+
77+
if (!$this->link) {
78+
return;
79+
}
80+
81+
foreach (array_merge(array($flattenException), $flattenException->getAllPrevious()) as $f) {
82+
$f->setExtra('trace_arguments', $this->flatten($variables));
83+
}
84+
}
85+
86+
private function getParameters($entry)
87+
{
88+
if (!isset($entry['function'])) {
89+
return array();
90+
}
91+
92+
try {
93+
if (isset($entry['class'])) {
94+
$ref = new \ReflectionMethod($entry['class'], $entry['function']);
95+
} else {
96+
$ref = new \ReflectionFunction($entry['function']);
97+
}
98+
} catch (\ReflectionException $e) {
99+
return array();
100+
}
101+
102+
return $ref->getParameters();
103+
}
104+
105+
private function flatten($variable)
106+
{
107+
if (null === $this->cloner) {
108+
return $this->flattenValue($variable);
109+
} else {
110+
return $this->cloner->cloneVar($variable);
111+
}
112+
}
113+
114+
private function flattenValue($value, $level = 0, &$count = 0)
115+
{
116+
if ($count++ > 1e4) {
117+
return array('array', '*SKIPPED over 10000 entries*');
118+
}
119+
120+
if (is_object($value)) {
121+
return array('object', get_class($value));
122+
}
123+
124+
if (is_array($value)) {
125+
if ($level > 10) {
126+
return array('array', '*DEEP NESTED ARRAY*');
127+
}
128+
129+
$array = array();
130+
foreach ($value as $k => $v) {
131+
$array[$k] = $this->flattenValue($v, $level + 1, $count);
132+
}
133+
134+
return array('array', $array);
135+
}
136+
137+
if (null === $value) {
138+
return array('null', null);
139+
}
140+
141+
if (is_bool($value)) {
142+
return array('boolean', $value);
143+
}
144+
145+
if (is_float($value) || is_int($value)) {
146+
return array('number', $value);
147+
}
148+
149+
if (is_resource($value)) {
150+
return array('resource', get_resource_type($value));
151+
}
152+
153+
if ($value instanceof \__PHP_Incomplete_Class) {
154+
// Special case of object, is_object will return false
155+
return array('incomplete-object', $this->getClassNameFromIncomplete($value));
156+
}
157+
158+
return array('string', (string) $value);
159+
}
160+
161+
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
162+
{
163+
$array = new \ArrayObject($value);
164+
165+
return $array['__PHP_Incomplete_Class_Name'];
166+
}
167+
}

src/Symfony/Component/Debug/Exception/FlattenException.php

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public function __call($method, $args)
5151

5252
namespace Symfony\Component\Debug\Exception;
5353

54+
use Symfony\Component\Debug\ArgumentsFlattenExceptionProcessor;
5455
use Symfony\Component\HttpKernel\Exception\FlattenException as LegacyFlattenException;
5556
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
5657

@@ -95,10 +96,14 @@ public static function create(\Exception $exception, $statusCode = null, array $
9596
$e->setClass(get_class($exception));
9697
$e->setFile($exception->getFile());
9798
$e->setLine($exception->getLine());
99+
98100
if ($exception->getPrevious()) {
99-
$e->setPrevious(static::create($exception->getPrevious()));
101+
$e->setPrevious(static::create($exception->getPrevious(), -1));
100102
}
101103

104+
$processor = new ArgumentsFlattenExceptionProcessor(null, false);
105+
$processor->process($exception, $e, -1 !== $statusCode);
106+
102107
return $e;
103108
}
104109

@@ -249,7 +254,7 @@ public function setTrace($trace, $file, $line)
249254
'function' => isset($entry['function']) ? $entry['function'] : null,
250255
'file' => isset($entry['file']) ? $entry['file'] : null,
251256
'line' => isset($entry['line']) ? $entry['line'] : null,
252-
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
257+
'args' => array(),
253258
);
254259
}
255260
}
@@ -297,43 +302,4 @@ public function setExtra($name, $value)
297302
{
298303
$this->extras[$name] = $value;
299304
}
300-
301-
private function flattenArgs($args, $level = 0, &$count = 0)
302-
{
303-
$result = array();
304-
foreach ($args as $key => $value) {
305-
if (++$count > 1e4) {
306-
return array('array', '*SKIPPED over 10000 entries*');
307-
}
308-
if (is_object($value)) {
309-
$result[$key] = array('object', get_class($value));
310-
} elseif (is_array($value)) {
311-
if ($level > 10) {
312-
$result[$key] = array('array', '*DEEP NESTED ARRAY*');
313-
} else {
314-
$result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count));
315-
}
316-
} elseif (null === $value) {
317-
$result[$key] = array('null', null);
318-
} elseif (is_bool($value)) {
319-
$result[$key] = array('boolean', $value);
320-
} elseif (is_resource($value)) {
321-
$result[$key] = array('resource', get_resource_type($value));
322-
} elseif ($value instanceof \__PHP_Incomplete_Class) {
323-
// Special case of object, is_object will return false
324-
$result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value));
325-
} else {
326-
$result[$key] = array('string', (string) $value);
327-
}
328-
}
329-
330-
return $result;
331-
}
332-
333-
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
334-
{
335-
$array = new \ArrayObject($value);
336-
337-
return $array['__PHP_Incomplete_Class_Name'];
338-
}
339305
}

src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -215,20 +215,21 @@ private function createException($foo)
215215

216216
public function testSetTraceIncompleteClass()
217217
{
218-
$flattened = FlattenException::create(new \Exception('test', 123));
219-
$flattened->setTrace(
218+
$exception = new \Exception('test', 123);
219+
$traceReflection = new \ReflectionProperty($exception, 'trace');
220+
$traceReflection->setAccessible(true);
221+
$traceReflection->setValue($exception, array(
220222
array(
221-
array(
222-
'file' => __FILE__,
223-
'line' => 123,
224-
'function' => 'test',
225-
'args' => array(
226-
unserialize('O:14:"BogusTestClass":0:{}'),
227-
),
223+
'file' => __FILE__,
224+
'line' => 123,
225+
'function' => 'test',
226+
'args' => array(
227+
unserialize('O:14:"BogusTestClass":0:{}'),
228228
),
229229
),
230-
'foo.php', 123
231-
);
230+
));
231+
232+
$flattened = FlattenException::create($exception);
232233

233234
$this->assertEquals(array(
234235
array(
@@ -237,7 +238,7 @@ public function testSetTraceIncompleteClass()
237238
'trace' => array(
238239
-1 => array(
239240
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '',
240-
'file' => 'foo.php', 'line' => 123,
241+
'file' => __FILE__, 'line' => 218,
241242
'args' => array(),
242243
),
243244
0 => array(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy