Skip to content

Commit c6e35d0

Browse files
[DebugBundle] dump() + better Symfony glue
1 parent a056e5a commit c6e35d0

File tree

2 files changed

+313
-0
lines changed

2 files changed

+313
-0
lines changed

DataCollector/DumpDataCollector.php

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\HttpKernel\DataCollector;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Component\Stopwatch\Stopwatch;
17+
use Symfony\Component\VarDumper\Cloner\Data;
18+
use Symfony\Component\VarDumper\Dumper\JsonDumper;
19+
use Symfony\Component\VarDumper\Dumper\CliDumper;
20+
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
21+
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
22+
23+
/**
24+
* @author Nicolas Grekas <[email protected]>
25+
*/
26+
class DumpDataCollector extends DataCollector implements DataDumperInterface
27+
{
28+
private $stopwatch;
29+
private $isCollected = true;
30+
private $clonesRoot;
31+
private $clonesCount = 0;
32+
33+
public function __construct(Stopwatch $stopwatch = null)
34+
{
35+
$this->stopwatch = $stopwatch;
36+
$this->clonesRoot = $this;
37+
}
38+
39+
public function __clone()
40+
{
41+
$this->data = array();
42+
$this->clonesRoot->clonesCount++;
43+
}
44+
45+
public function dump(Data $data)
46+
{
47+
if ($this->stopwatch) {
48+
$this->stopwatch->start('dump');
49+
}
50+
if ($this->clonesRoot->isCollected) {
51+
$this->clonesRoot->isCollected = false;
52+
register_shutdown_function(array($this->clonesRoot, 'flushDumps'));
53+
}
54+
55+
$trace = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS : true;
56+
if (PHP_VERSION_ID >= 50400) {
57+
$trace = debug_backtrace($trace, 6);
58+
} else {
59+
$trace = debug_backtrace($trace);
60+
}
61+
62+
$file = $trace[0]['file'];
63+
$line = $trace[0]['line'];
64+
$name = false;
65+
$fileExcerpt = false;
66+
67+
for ($i = 1; $i < 6; ++$i) {
68+
if (isset($trace[$i]['class'], $trace[$i]['function'])
69+
&& 'dump' === $trace[$i]['function']
70+
&& 'Symfony\Bundle\DebugBundle\DebugBundle' === $trace[$i]['class']
71+
) {
72+
$file = $trace[$i]['file'];
73+
$line = $trace[$i]['line'];
74+
75+
while (++$i < 6) {
76+
if (isset($trace[$i]['function']) && empty($trace[$i]['class'])) {
77+
$file = $trace[$i]['file'];
78+
$line = $trace[$i]['line'];
79+
break;
80+
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) {
81+
$info = $trace[$i]['object'];
82+
$name = $info->getTemplateName();
83+
$src = $info->getEnvironment()->getLoader()->getSource($name);
84+
$info = $info->getDebugInfo();
85+
if (isset($info[$trace[$i-1]['line']])) {
86+
$file = false;
87+
$line = $info[$trace[$i-1]['line']];
88+
$src = explode("\n", $src);
89+
$fileExcerpt = array();
90+
91+
for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) {
92+
$fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.htmlspecialchars($src[$i - 1]).'</code></li>';
93+
}
94+
95+
$fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>';
96+
}
97+
break;
98+
}
99+
}
100+
break;
101+
}
102+
}
103+
104+
if (false === $name) {
105+
$name = strtr($file, '\\', '/');
106+
$name = substr($file, strrpos($file, '/') + 1);
107+
}
108+
109+
$this->clonesRoot->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
110+
111+
if ($this->stopwatch) {
112+
$this->stopwatch->stop('dump');
113+
}
114+
}
115+
116+
public function collect(Request $request, Response $response, \Exception $exception = null)
117+
{
118+
}
119+
120+
public function serialize()
121+
{
122+
$ser = serialize($this->clonesRoot->data);
123+
$this->clonesRoot->data = array();
124+
$this->clonesRoot->isCollected = true;
125+
126+
return $ser;
127+
}
128+
129+
public function unserialize($data)
130+
{
131+
parent::unserialize($data);
132+
133+
$this->clonesRoot = $this;
134+
}
135+
136+
public function getDumpsCount()
137+
{
138+
return count($this->clonesRoot->data);
139+
}
140+
141+
public function getDumpsExcerpts()
142+
{
143+
$dumps = array();
144+
145+
foreach ($this->data as $dump) {
146+
$data = $dump['data']->getRawData();
147+
unset($dump['data']);
148+
149+
$data = $data[0][0];
150+
151+
if (isset($data->val)) {
152+
$data = $data->val;
153+
}
154+
155+
if (isset($data->bin)) {
156+
$data = 'b"'.$data->bin.'"';
157+
} elseif (isset($data->str)) {
158+
$data = '"'.$data->str.'"';
159+
} elseif (isset($data->count)) {
160+
$data = 'array('.$data->count.')';
161+
} elseif (isset($data->class)) {
162+
$data = $data->class.'{...}';
163+
} elseif (isset($data->res)) {
164+
$data = 'resource:'.$data->res.'{...}';
165+
} elseif (is_array($data)) {
166+
$data = 'array()';
167+
} elseif (null === $data) {
168+
$data = 'null';
169+
} elseif (false === $data) {
170+
$data = 'false';
171+
} elseif (INF === $data) {
172+
$data = 'INF';
173+
} elseif (-INF === $data) {
174+
$data = '-INF';
175+
} elseif (NAN === $data) {
176+
$data = 'NAN';
177+
} elseif (true === $data) {
178+
$data = 'true';
179+
}
180+
181+
$dump['dataExcerpt'] = $data;
182+
$dumps[] = $dump;
183+
}
184+
185+
return $dumps;
186+
}
187+
188+
public function getDumps($getData = false)
189+
{
190+
if ($getData) {
191+
$dumper = new JsonDumper();
192+
}
193+
$dumps = array();
194+
195+
foreach ($this->clonesRoot->data as $dump) {
196+
$json = '';
197+
if ($getData) {
198+
$dumper->dump($dump['data'], function ($line) use (&$json) {$json .= $line;});
199+
}
200+
$dump['data'] = $json;
201+
$dumps[] = $dump;
202+
}
203+
204+
return $dumps;
205+
}
206+
207+
public function getName()
208+
{
209+
return 'dump';
210+
}
211+
212+
public function flushDumps()
213+
{
214+
if (0 === $this->clonesRoot->clonesCount-- && !$this->clonesRoot->isCollected && $this->clonesRoot->data) {
215+
$this->clonesRoot->clonesCount = 0;
216+
$this->clonesRoot->isCollected = true;
217+
218+
$h = headers_list();
219+
$i = count($h);
220+
array_unshift($h, 'Content-Type: ' . ini_get('default_mimetype'));
221+
while (0 !== stripos($h[$i], 'Content-Type:')) {
222+
--$i;
223+
}
224+
225+
if (stripos($h[$i], 'html')) {
226+
echo '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
227+
$dumper = new HtmlDumper();
228+
} else {
229+
$dumper = new CliDumper();
230+
$dumper->setColors(false);
231+
}
232+
233+
foreach ($this->clonesRoot->data as $i => $dump) {
234+
$this->clonesRoot->data[$i] = null;
235+
236+
if ($dumper instanceof HtmlDumper) {
237+
$dump['name'] = htmlspecialchars($dump['name'], ENT_QUOTES, 'UTF-8');
238+
$dump['file'] = htmlspecialchars($dump['file'], ENT_QUOTES, 'UTF-8');
239+
if ('' !== $dump['file']) {
240+
$dump['name'] = "<abbr title=\"{$dump['file']}\">{$dump['name']}</abbr>";
241+
}
242+
echo "\n<br><span class=\"sf-dump-meta\">in {$dump['name']} on line {$dump['line']}:</span>";
243+
} else {
244+
echo "\nin {$dump['name']} on line {$dump['line']}:\n\n";
245+
}
246+
$dumper->dump($dump['data']);
247+
}
248+
249+
$this->clonesRoot->data = array();
250+
}
251+
}
252+
}

EventListener/DumpListener.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\HttpKernel\EventListener;
13+
14+
use Symfony\Component\Debug\Debug;
15+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpKernel\KernelEvents;
17+
use Symfony\Component\DependencyInjection\ContainerInterface;
18+
19+
/**
20+
* Configures dump() handler.
21+
*
22+
* @author Nicolas Grekas <[email protected]>
23+
*/
24+
class DumpListener implements EventSubscriberInterface
25+
{
26+
private $container;
27+
private $dumper;
28+
29+
/**
30+
* @param ContainerInterface $container Service container, for lazy loading.
31+
* @param string $dumper var_dumper dumper service to use.
32+
*/
33+
public function __construct(ContainerInterface $container, $dumper)
34+
{
35+
$this->container = $container;
36+
$this->dumper = $dumper;
37+
}
38+
39+
public function configure()
40+
{
41+
if ($this->container) {
42+
$container = $this->container;
43+
$dumper = $this->dumper;
44+
$this->container = null;
45+
46+
Debug::setDumpHandler(function ($var) use ($container, $dumper) {
47+
$dumper = $container->get($dumper);
48+
$cloner = $container->get('var_dumper.cloner');
49+
$handler = function ($var) use ($dumper, $cloner) {$dumper->dump($cloner->cloneVar($var));};
50+
Debug::setDumpHandler($handler);
51+
$handler($var);
52+
});
53+
}
54+
}
55+
56+
public static function getSubscribedEvents()
57+
{
58+
// Register early to have a working dump() as early as possible
59+
return array(KernelEvents::REQUEST => array('configure', 1024));
60+
}
61+
}

0 commit comments

Comments
 (0)