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\RequestStack; |
16
|
|
|
use Symfony\Component\HttpFoundation\Response; |
17
|
|
|
use Symfony\Component\Stopwatch\Stopwatch; |
18
|
|
|
use Symfony\Component\VarDumper\Cloner\Data; |
19
|
|
|
use Symfony\Component\VarDumper\Cloner\VarCloner; |
20
|
|
|
use Symfony\Component\VarDumper\Dumper\CliDumper; |
21
|
|
|
use Symfony\Component\VarDumper\Dumper\HtmlDumper; |
22
|
|
|
use Symfony\Component\VarDumper\Dumper\DataDumperInterface; |
23
|
|
|
use Twig\Template; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @author Nicolas Grekas <[email protected]> |
27
|
|
|
*/ |
28
|
|
|
class DumpDataCollector extends DataCollector implements DataDumperInterface |
29
|
|
|
{ |
30
|
|
|
private $stopwatch; |
31
|
|
|
private $fileLinkFormat; |
32
|
|
|
private $dataCount = 0; |
33
|
|
|
private $isCollected = true; |
34
|
|
|
private $clonesCount = 0; |
35
|
|
|
private $clonesIndex = 0; |
36
|
|
|
private $rootRefs; |
37
|
|
|
private $charset; |
38
|
|
|
private $requestStack; |
39
|
|
|
private $dumper; |
40
|
|
|
private $dumperIsInjected; |
41
|
|
|
|
42
|
|
|
public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null) |
43
|
|
|
{ |
44
|
|
|
$this->stopwatch = $stopwatch; |
45
|
|
|
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); |
46
|
|
|
$this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'; |
47
|
|
|
$this->requestStack = $requestStack; |
48
|
|
|
$this->dumper = $dumper; |
49
|
|
|
$this->dumperIsInjected = null !== $dumper; |
50
|
|
|
|
51
|
|
|
// All clones share these properties by reference: |
52
|
|
|
$this->rootRefs = array( |
53
|
|
|
&$this->data, |
54
|
|
|
&$this->dataCount, |
55
|
|
|
&$this->isCollected, |
56
|
|
|
&$this->clonesCount, |
57
|
|
|
); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
public function __clone() |
61
|
|
|
{ |
62
|
|
|
$this->clonesIndex = ++$this->clonesCount; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
public function dump(Data $data) |
66
|
|
|
{ |
67
|
|
|
if ($this->stopwatch) { |
68
|
|
|
$this->stopwatch->start('dump'); |
69
|
|
|
} |
70
|
|
|
if ($this->isCollected) { |
71
|
|
|
$this->isCollected = false; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$trace = DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS; |
75
|
|
|
if (\PHP_VERSION_ID >= 50400) { |
76
|
|
|
$trace = debug_backtrace($trace, 7); |
77
|
|
|
} else { |
78
|
|
|
$trace = debug_backtrace($trace); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
$file = $trace[0]['file']; |
82
|
|
|
$line = $trace[0]['line']; |
83
|
|
|
$name = false; |
84
|
|
|
$fileExcerpt = false; |
85
|
|
|
|
86
|
|
|
for ($i = 1; $i < 7; ++$i) { |
87
|
|
|
if (isset($trace[$i]['class'], $trace[$i]['function']) |
88
|
|
|
&& 'dump' === $trace[$i]['function'] |
89
|
|
|
&& 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] |
90
|
|
|
) { |
91
|
|
|
$file = $trace[$i]['file']; |
92
|
|
|
$line = $trace[$i]['line']; |
93
|
|
|
|
94
|
|
|
while (++$i < 7) { |
95
|
|
|
if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) { |
96
|
|
|
$file = $trace[$i]['file']; |
97
|
|
|
$line = $trace[$i]['line']; |
98
|
|
|
|
99
|
|
|
break; |
100
|
|
|
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { |
|
|
|
|
101
|
|
|
$template = $trace[$i]['object']; |
102
|
|
|
$name = $template->getTemplateName(); |
103
|
|
|
$src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); |
104
|
|
|
$info = $template->getDebugInfo(); |
105
|
|
|
if (isset($info[$trace[$i - 1]['line']])) { |
106
|
|
|
$line = $info[$trace[$i - 1]['line']]; |
107
|
|
|
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : false; |
108
|
|
|
|
109
|
|
|
if ($src) { |
110
|
|
|
$src = explode("\n", $src); |
111
|
|
|
$fileExcerpt = array(); |
112
|
|
|
|
113
|
|
|
for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) { |
114
|
|
|
$fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>'; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
$fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>'; |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
break; |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
break; |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
if (false === $name) { |
128
|
|
|
$name = str_replace('\\', '/', $file); |
129
|
|
|
$name = substr($name, strrpos($name, '/') + 1); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
if ($this->dumper) { |
133
|
|
|
$this->doDump($data, $name, $file, $line); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); |
137
|
|
|
++$this->dataCount; |
138
|
|
|
|
139
|
|
|
if ($this->stopwatch) { |
140
|
|
|
$this->stopwatch->stop('dump'); |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
public function collect(Request $request, Response $response, \Exception $exception = null) |
145
|
|
|
{ |
146
|
|
|
// Sub-requests and programmatic calls stay in the collected profile. |
147
|
|
|
if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { |
148
|
|
|
return; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
// In all other conditions that remove the web debug toolbar, dumps are written on the output. |
152
|
|
|
if (!$this->requestStack |
153
|
|
|
|| !$response->headers->has('X-Debug-Token') |
154
|
|
|
|| $response->isRedirection() |
155
|
|
|
|| ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) |
156
|
|
|
|| 'html' !== $request->getRequestFormat() |
157
|
|
|
|| false === strripos($response->getContent(), '</body>') |
158
|
|
|
) { |
159
|
|
|
if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) { |
160
|
|
|
$this->dumper = new HtmlDumper('php://output', $this->charset); |
161
|
|
|
} else { |
162
|
|
|
$this->dumper = new CliDumper('php://output', $this->charset); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
foreach ($this->data as $dump) { |
166
|
|
|
$this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function serialize() |
172
|
|
|
{ |
173
|
|
|
if ($this->clonesCount !== $this->clonesIndex) { |
174
|
|
|
return 'a:0:{}'; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$this->data[] = $this->fileLinkFormat; |
178
|
|
|
$this->data[] = $this->charset; |
179
|
|
|
$ser = serialize($this->data); |
180
|
|
|
$this->data = array(); |
181
|
|
|
$this->dataCount = 0; |
182
|
|
|
$this->isCollected = true; |
183
|
|
|
if (!$this->dumperIsInjected) { |
184
|
|
|
$this->dumper = null; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return $ser; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
public function unserialize($data) |
191
|
|
|
{ |
192
|
|
|
parent::unserialize($data); |
193
|
|
|
$charset = array_pop($this->data); |
194
|
|
|
$fileLinkFormat = array_pop($this->data); |
195
|
|
|
$this->dataCount = count($this->data); |
196
|
|
|
self::__construct($this->stopwatch, $fileLinkFormat, $charset); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
public function getDumpsCount() |
200
|
|
|
{ |
201
|
|
|
return $this->dataCount; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) |
205
|
|
|
{ |
206
|
|
|
$data = fopen('php://memory', 'r+b'); |
207
|
|
|
|
208
|
|
|
if ('html' === $format) { |
209
|
|
|
$dumper = new HtmlDumper($data, $this->charset); |
210
|
|
|
} else { |
211
|
|
|
throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format)); |
212
|
|
|
} |
213
|
|
|
$dumps = array(); |
214
|
|
|
|
215
|
|
|
foreach ($this->data as $dump) { |
216
|
|
|
if (method_exists($dump['data'], 'withMaxDepth')) { |
217
|
|
|
$dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); |
218
|
|
|
} else { |
219
|
|
|
// getLimitedClone is @deprecated, to be removed in 3.0 |
220
|
|
|
$dumper->dump($dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth)); |
221
|
|
|
} |
222
|
|
|
$dump['data'] = stream_get_contents($data, -1, 0); |
223
|
|
|
ftruncate($data, 0); |
224
|
|
|
rewind($data); |
225
|
|
|
$dumps[] = $dump; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
return $dumps; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
public function getName() |
232
|
|
|
{ |
233
|
|
|
return 'dump'; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
public function __destruct() |
237
|
|
|
{ |
238
|
|
|
if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) { |
|
|
|
|
239
|
|
|
$this->clonesCount = 0; |
240
|
|
|
$this->isCollected = true; |
241
|
|
|
|
242
|
|
|
$h = headers_list(); |
243
|
|
|
$i = count($h); |
244
|
|
|
array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); |
245
|
|
|
while (0 !== stripos($h[$i], 'Content-Type:')) { |
246
|
|
|
--$i; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) { |
250
|
|
|
$this->dumper = new HtmlDumper('php://output', $this->charset); |
251
|
|
|
} else { |
252
|
|
|
$this->dumper = new CliDumper('php://output', $this->charset); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
foreach ($this->data as $i => $dump) { |
256
|
|
|
$this->data[$i] = null; |
257
|
|
|
$this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
$this->data = array(); |
261
|
|
|
$this->dataCount = 0; |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
private function doDump($data, $name, $file, $line) |
266
|
|
|
{ |
267
|
|
|
if (\PHP_VERSION_ID >= 50400 && $this->dumper instanceof CliDumper) { |
|
|
|
|
268
|
|
|
$contextDumper = function ($name, $file, $line, $fileLinkFormat) { |
269
|
|
|
if ($this instanceof HtmlDumper) { |
|
|
|
|
270
|
|
|
if ('' !== $file) { |
271
|
|
|
$s = $this->style('meta', '%s'); |
272
|
|
|
$name = strip_tags($this->style('', $name)); |
273
|
|
|
$file = strip_tags($this->style('', $file)); |
274
|
|
|
if ($fileLinkFormat) { |
275
|
|
|
$link = strtr(strip_tags($this->style('', $fileLinkFormat)), array('%f' => $file, '%l' => (int) $line)); |
276
|
|
|
$name = sprintf('<a href="%s" title="%s">'.$s.'</a>', $link, $file, $name); |
277
|
|
|
} else { |
278
|
|
|
$name = sprintf('<abbr title="%s">'.$s.'</abbr>', $file, $name); |
279
|
|
|
} |
280
|
|
|
} else { |
281
|
|
|
$name = $this->style('meta', $name); |
282
|
|
|
} |
283
|
|
|
$this->line = $name.' on line '.$this->style('meta', $line).':'; |
284
|
|
|
} else { |
285
|
|
|
$this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; |
286
|
|
|
} |
287
|
|
|
$this->dumpLine(0); |
288
|
|
|
}; |
289
|
|
|
$contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper); |
290
|
|
|
$contextDumper($name, $file, $line, $this->fileLinkFormat); |
291
|
|
|
} else { |
292
|
|
|
$cloner = new VarCloner(); |
293
|
|
|
$this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); |
294
|
|
|
} |
295
|
|
|
$this->dumper->dump($data); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
private function htmlEncode($s) |
299
|
|
|
{ |
300
|
|
|
$html = ''; |
301
|
|
|
|
302
|
|
|
$dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); |
303
|
|
|
$dumper->setDumpHeader(''); |
304
|
|
|
$dumper->setDumpBoundaries('', ''); |
305
|
|
|
|
306
|
|
|
$cloner = new VarCloner(); |
307
|
|
|
$dumper->dump($cloner->cloneVar($s)); |
308
|
|
|
|
309
|
|
|
return substr(strip_tags($html), 1, -1); |
310
|
|
|
} |
311
|
|
|
} |
312
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.