1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of the TYPO3 CMS project. |
7
|
|
|
* |
8
|
|
|
* It is free software; you can redistribute it and/or modify it under |
9
|
|
|
* the terms of the GNU General Public License, either version 2 |
10
|
|
|
* of the License, or any later version. |
11
|
|
|
* |
12
|
|
|
* For the full copyright and license information, please read the |
13
|
|
|
* LICENSE.txt file that was distributed with this source code. |
14
|
|
|
* |
15
|
|
|
* The TYPO3 project - inspiring people to share! |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
namespace TYPO3\CMS\Extbase\Utility; |
19
|
|
|
|
20
|
|
|
use TYPO3\CMS\Core\SingletonInterface; |
21
|
|
|
use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject; |
22
|
|
|
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; |
23
|
|
|
use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject; |
24
|
|
|
use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface; |
25
|
|
|
use TYPO3\CMS\Extbase\Object\ObjectManager; |
26
|
|
|
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy; |
27
|
|
|
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; |
28
|
|
|
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager; |
29
|
|
|
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory; |
30
|
|
|
use TYPO3\CMS\Extbase\Persistence\ObjectStorage; |
31
|
|
|
use TYPO3\CMS\Extbase\Reflection\ReflectionService; |
32
|
|
|
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* This class is a backport of the corresponding class of TYPO3 Flow. |
36
|
|
|
* All credits go to the TYPO3 Flow team. |
37
|
|
|
* |
38
|
|
|
* A debugging utility class |
39
|
|
|
*/ |
40
|
|
|
class DebuggerUtility |
41
|
|
|
{ |
42
|
|
|
const PLAINTEXT_INDENT = ' '; |
43
|
|
|
const HTML_INDENT = ' '; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage |
47
|
|
|
*/ |
48
|
|
|
protected static $renderedObjects; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Hardcoded list of Extbase class names (regex) which should not be displayed during debugging |
52
|
|
|
* |
53
|
|
|
* @var array |
54
|
|
|
*/ |
55
|
|
|
protected static $blacklistedClassNames = [ |
56
|
|
|
'PHPUnit_Framework_MockObject_InvocationMocker', |
57
|
|
|
ReflectionService::class, |
58
|
|
|
// @deprecated since v11, will be removed in v12. |
59
|
|
|
ObjectManager::class, |
60
|
|
|
DataMapper::class, |
61
|
|
|
PersistenceManager::class, |
62
|
|
|
QueryObjectModelFactory::class, |
63
|
|
|
ContentObjectRenderer::class |
64
|
|
|
]; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Hardcoded list of property names (regex) which should not be displayed during debugging |
68
|
|
|
* |
69
|
|
|
* @var array |
70
|
|
|
*/ |
71
|
|
|
protected static $blacklistedPropertyNames = ['warning']; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Is set to TRUE once the CSS file is included in the current page to prevent double inclusions of the CSS file. |
75
|
|
|
* |
76
|
|
|
* @var bool |
77
|
|
|
*/ |
78
|
|
|
protected static $stylesheetEchoed = false; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Defines the max recursion depth of the dump, set to 8 due to common memory limits |
82
|
|
|
* |
83
|
|
|
* @var int |
84
|
|
|
*/ |
85
|
|
|
protected static $maxDepth = 8; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Clear the state of the debugger |
89
|
|
|
*/ |
90
|
|
|
protected static function clearState(): void |
91
|
|
|
{ |
92
|
|
|
self::$renderedObjects = new ObjectStorage(); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Renders a dump of the given value |
97
|
|
|
* |
98
|
|
|
* @param mixed $value |
99
|
|
|
* @param int $level |
100
|
|
|
* @param bool $plainText |
101
|
|
|
* @param bool $ansiColors |
102
|
|
|
* @return string |
103
|
|
|
*/ |
104
|
|
|
protected static function renderDump($value, int $level, bool $plainText, bool $ansiColors): string |
105
|
|
|
{ |
106
|
|
|
$dump = ''; |
107
|
|
|
if (is_string($value)) { |
108
|
|
|
$croppedValue = strlen($value) > 2000 ? substr($value, 0, 2000) . '...' : $value; |
109
|
|
|
if ($plainText) { |
110
|
|
|
$dump = self::ansiEscapeWrap('"' . implode(PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level + 1), str_split($croppedValue, 76)) . '"', '33', $ansiColors) . ' (' . strlen($value) . ' chars)'; |
|
|
|
|
111
|
|
|
} else { |
112
|
|
|
$lines = str_split($croppedValue, 76); |
113
|
|
|
$lines = array_map(static function (string $line): string { |
114
|
|
|
return htmlspecialchars($line, ENT_COMPAT); |
115
|
|
|
}, $lines); |
|
|
|
|
116
|
|
|
$dump = sprintf('\'<span class="extbase-debug-string">%s</span>\' (%s chars)', implode('<br />' . str_repeat(self::HTML_INDENT, $level + 1), $lines), strlen($value)); |
117
|
|
|
} |
118
|
|
|
} elseif (is_numeric($value)) { |
119
|
|
|
$dump = sprintf('%s (%s)', self::ansiEscapeWrap((string)$value, '35', $ansiColors), gettype($value)); |
120
|
|
|
} elseif (is_bool($value)) { |
121
|
|
|
$dump = $value ? self::ansiEscapeWrap('TRUE', '32', $ansiColors) : self::ansiEscapeWrap('FALSE', '32', $ansiColors); |
122
|
|
|
} elseif ($value === null || is_resource($value)) { |
123
|
|
|
$dump = gettype($value); |
124
|
|
|
} elseif (is_array($value)) { |
125
|
|
|
$dump = self::renderArray($value, $level + 1, $plainText, $ansiColors); |
126
|
|
|
} elseif (is_object($value)) { |
127
|
|
|
if ($value instanceof \Closure) { |
128
|
|
|
$dump = self::renderClosure($value, $level + 1, $plainText, $ansiColors); |
129
|
|
|
} else { |
130
|
|
|
$dump = self::renderObject($value, $level + 1, $plainText, $ansiColors); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
return $dump; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Renders a dump of the given array |
138
|
|
|
* |
139
|
|
|
* @param array $array |
140
|
|
|
* @param int $level |
141
|
|
|
* @param bool $plainText |
142
|
|
|
* @param bool $ansiColors |
143
|
|
|
* @return string |
144
|
|
|
*/ |
145
|
|
|
protected static function renderArray(array $array, int $level, bool $plainText = false, bool $ansiColors = false): string |
146
|
|
|
{ |
147
|
|
|
$content = ''; |
148
|
|
|
$count = count($array); |
149
|
|
|
|
150
|
|
|
if ($plainText) { |
151
|
|
|
$header = self::ansiEscapeWrap('array', '36', $ansiColors); |
152
|
|
|
} else { |
153
|
|
|
$header = '<span class="extbase-debug-type">array</span>'; |
154
|
|
|
} |
155
|
|
|
$header .= $count > 0 ? '(' . $count . ' item' . ($count > 1 ? 's' : '') . ')' : '(empty)'; |
156
|
|
|
if ($level >= self::$maxDepth) { |
157
|
|
|
if ($plainText) { |
158
|
|
|
$header .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors); |
159
|
|
|
} else { |
160
|
|
|
$header .= '<span class="extbase-debug-filtered">max depth</span>'; |
161
|
|
|
} |
162
|
|
|
} else { |
163
|
|
|
$content = self::renderCollection($array, $level, $plainText, $ansiColors); |
164
|
|
|
if (!$plainText) { |
165
|
|
|
$header = ($level > 1 && $count > 0 ? '<input type="checkbox" /><span class="extbase-debug-header" >' : '<span>') . $header . '</span >'; |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
if ($level > 1 && $count > 0 && !$plainText) { |
169
|
|
|
$dump = '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>'; |
170
|
|
|
} else { |
171
|
|
|
$dump = $header . $content; |
172
|
|
|
} |
173
|
|
|
return $dump; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Renders a dump of the given object |
178
|
|
|
* |
179
|
|
|
* @param object $object |
180
|
|
|
* @param int $level |
181
|
|
|
* @param bool $plainText |
182
|
|
|
* @param bool $ansiColors |
183
|
|
|
* @return string |
184
|
|
|
*/ |
185
|
|
|
protected static function renderObject(object $object, int $level, bool $plainText = false, bool $ansiColors = false): string |
186
|
|
|
{ |
187
|
|
|
if ($object instanceof LazyLoadingProxy) { |
188
|
|
|
$object = $object->_loadRealInstance(); |
189
|
|
|
if (!is_object($object)) { |
190
|
|
|
return gettype($object); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
$header = self::renderHeader($object, $level, $plainText, $ansiColors); |
194
|
|
|
if ($level < self::$maxDepth && !self::isBlacklisted($object) && !(self::isAlreadyRendered($object) && $plainText !== true)) { |
195
|
|
|
$content = self::renderContent($object, $level, $plainText, $ansiColors); |
196
|
|
|
} else { |
197
|
|
|
$content = ''; |
198
|
|
|
} |
199
|
|
|
if ($plainText) { |
200
|
|
|
return $header . $content; |
201
|
|
|
} |
202
|
|
|
return '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>'; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Renders a dump of the given closure |
207
|
|
|
* |
208
|
|
|
* @param \Closure $object |
209
|
|
|
* @param int $level |
210
|
|
|
* @param bool $plainText |
211
|
|
|
* @param bool $ansiColors |
212
|
|
|
* @return string |
213
|
|
|
*/ |
214
|
|
|
protected static function renderClosure(\Closure $object, int $level, bool $plainText = false, bool $ansiColors = false): string |
215
|
|
|
{ |
216
|
|
|
$header = self::renderHeader($object, $level, $plainText, $ansiColors); |
217
|
|
|
if ($level < self::$maxDepth && (!self::isAlreadyRendered($object) || $plainText)) { |
218
|
|
|
$content = self::renderContent($object, $level, $plainText, $ansiColors); |
219
|
|
|
} else { |
220
|
|
|
$content = ''; |
221
|
|
|
} |
222
|
|
|
if ($plainText) { |
223
|
|
|
return $header . $content; |
224
|
|
|
} |
225
|
|
|
return '<span class="extbase-debugger-tree"><input type="checkbox" /><span class="extbase-debug-header">' . $header . '</span><span class="extbase-debug-content">' . $content . '</span></span>'; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Checks if a given object or property should be excluded/filtered |
230
|
|
|
* |
231
|
|
|
* @param object $value A ReflectionProperty or other Object |
232
|
|
|
* @return bool TRUE if the given object should be filtered |
233
|
|
|
*/ |
234
|
|
|
protected static function isBlacklisted(object $value): bool |
235
|
|
|
{ |
236
|
|
|
if ($value instanceof \ReflectionProperty) { |
237
|
|
|
$result = in_array($value->getName(), self::$blacklistedPropertyNames, true); |
238
|
|
|
} else { |
239
|
|
|
$result = in_array(get_class($value), self::$blacklistedClassNames, true); |
240
|
|
|
} |
241
|
|
|
return $result; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Checks if a given object was already rendered. |
246
|
|
|
* |
247
|
|
|
* @param object $object |
248
|
|
|
* @return bool TRUE if the given object was already rendered |
249
|
|
|
*/ |
250
|
|
|
protected static function isAlreadyRendered(object $object): bool |
251
|
|
|
{ |
252
|
|
|
return self::$renderedObjects->contains($object); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Renders the header of a given object/collection. It is usually the class name along with some flags. |
257
|
|
|
* |
258
|
|
|
* @param object $object |
259
|
|
|
* @param int $level |
260
|
|
|
* @param bool $plainText |
261
|
|
|
* @param bool $ansiColors |
262
|
|
|
* @return string The rendered header with tags |
263
|
|
|
*/ |
264
|
|
|
protected static function renderHeader(object $object, int $level, bool $plainText, bool $ansiColors): string |
265
|
|
|
{ |
266
|
|
|
$dump = ''; |
267
|
|
|
$persistenceType = null; |
268
|
|
|
$className = get_class($object); |
269
|
|
|
$classReflection = new \ReflectionClass($className); |
270
|
|
|
if ($plainText) { |
271
|
|
|
$dump .= self::ansiEscapeWrap($className, '36', $ansiColors); |
272
|
|
|
} else { |
273
|
|
|
$dump .= '<span class="extbase-debug-type">' . htmlspecialchars($className, ENT_COMPAT) . '</span>'; |
274
|
|
|
} |
275
|
|
|
if (!$object instanceof \Closure) { |
276
|
|
|
if ($object instanceof SingletonInterface) { |
277
|
|
|
$scope = 'singleton'; |
278
|
|
|
} else { |
279
|
|
|
$scope = 'prototype'; |
280
|
|
|
} |
281
|
|
|
if ($plainText) { |
282
|
|
|
$dump .= ' ' . self::ansiEscapeWrap($scope, '44;37', $ansiColors); |
283
|
|
|
} else { |
284
|
|
|
$dump .= '<span class="extbase-debug-scope">' . $scope . '</span>'; |
285
|
|
|
} |
286
|
|
|
if ($object instanceof AbstractDomainObject) { |
287
|
|
|
if ($object->_isDirty()) { |
288
|
|
|
$persistenceType = 'modified'; |
289
|
|
|
} elseif ($object->_isNew()) { |
290
|
|
|
$persistenceType = 'transient'; |
291
|
|
|
} else { |
292
|
|
|
$persistenceType = 'persistent'; |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
if ($object instanceof ObjectStorage && $object->_isDirty()) { |
296
|
|
|
$persistenceType = 'modified'; |
297
|
|
|
} |
298
|
|
|
if ($object instanceof AbstractEntity) { |
299
|
|
|
$domainObjectType = 'entity'; |
300
|
|
|
} elseif ($object instanceof AbstractValueObject) { |
301
|
|
|
$domainObjectType = 'valueobject'; |
302
|
|
|
} else { |
303
|
|
|
$domainObjectType = 'object'; |
304
|
|
|
} |
305
|
|
|
$persistenceType = $persistenceType === null ? '' : $persistenceType . ' '; |
306
|
|
|
if ($plainText) { |
307
|
|
|
$dump .= ' ' . self::ansiEscapeWrap($persistenceType . $domainObjectType, '42;30', $ansiColors); |
308
|
|
|
} else { |
309
|
|
|
$dump .= '<span class="extbase-debug-ptype">' . $persistenceType . $domainObjectType . '</span>'; |
310
|
|
|
} |
311
|
|
|
} |
312
|
|
|
if (strpos(implode('|', self::$blacklistedClassNames), get_class($object)) > 0) { |
313
|
|
|
if ($plainText) { |
314
|
|
|
$dump .= ' ' . self::ansiEscapeWrap('filtered', '47;30', $ansiColors); |
315
|
|
|
} else { |
316
|
|
|
$dump .= '<span class="extbase-debug-filtered">filtered</span>'; |
317
|
|
|
} |
318
|
|
|
} elseif (self::$renderedObjects->contains($object) && !$plainText) { |
319
|
|
|
$dump = '<a href="javascript:;" onclick="document.location.hash=\'#' . spl_object_hash($object) . '\';" class="extbase-debug-seeabove">' . $dump . '<span class="extbase-debug-filtered">see above</span></a>'; |
320
|
|
|
} elseif ($level >= self::$maxDepth && !$object instanceof \DateTimeInterface) { |
321
|
|
|
if ($plainText) { |
322
|
|
|
$dump .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors); |
323
|
|
|
} else { |
324
|
|
|
$dump .= '<span class="extbase-debug-filtered">max depth</span>'; |
325
|
|
|
} |
326
|
|
|
} elseif ($level > 1 && !$object instanceof \DateTimeInterface && !$plainText) { |
327
|
|
|
if (($object instanceof \Countable && empty($object)) || empty($classReflection->getProperties())) { |
328
|
|
|
$dump = '<span>' . $dump . '</span>'; |
329
|
|
|
} else { |
330
|
|
|
$dump = '<input type="checkbox" id="' . spl_object_hash($object) . '" /><span class="extbase-debug-header">' . $dump . '</span>'; |
331
|
|
|
} |
332
|
|
|
} |
333
|
|
|
if ($object instanceof \Countable) { |
334
|
|
|
$objectCount = count($object); |
335
|
|
|
$dump .= $objectCount > 0 ? ' (' . $objectCount . ' items)' : ' (empty)'; |
336
|
|
|
} |
337
|
|
|
if ($object instanceof \DateTimeInterface) { |
338
|
|
|
$dump .= ' (' . $object->format(\DateTimeInterface::RFC3339) . ', ' . $object->getTimestamp() . ')'; |
339
|
|
|
} |
340
|
|
|
if ($object instanceof DomainObjectInterface && !$object->_isNew()) { |
341
|
|
|
$dump .= ' (uid=' . $object->getUid() . ', pid=' . $object->getPid() . ')'; |
342
|
|
|
} |
343
|
|
|
return $dump; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* @param object $object |
348
|
|
|
* @param int $level |
349
|
|
|
* @param bool $plainText |
350
|
|
|
* @param bool $ansiColors |
351
|
|
|
* @return string The rendered body content of the Object(Storage) |
352
|
|
|
*/ |
353
|
|
|
protected static function renderContent(object $object, int $level, bool $plainText, bool $ansiColors): string |
354
|
|
|
{ |
355
|
|
|
$dump = ''; |
356
|
|
|
if ($object instanceof \Iterator || $object instanceof \ArrayObject) { |
357
|
|
|
$dump .= self::renderCollection($object, $level, $plainText, $ansiColors); |
358
|
|
|
} else { |
359
|
|
|
self::$renderedObjects->attach($object); |
360
|
|
|
if (!$plainText) { |
361
|
|
|
$dump .= '<a name="' . spl_object_hash($object) . '" id="' . spl_object_hash($object) . '"></a>'; |
362
|
|
|
} |
363
|
|
|
if ($object instanceof \Closure) { |
364
|
|
|
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level) |
365
|
|
|
. ($plainText ? '' : '<span class="extbase-debug-closure">') |
366
|
|
|
. self::ansiEscapeWrap('function (', '33', $ansiColors) . ($plainText ? '' : '</span>'); |
367
|
|
|
|
368
|
|
|
$reflectionFunction = new \ReflectionFunction($object); |
369
|
|
|
$params = []; |
370
|
|
|
foreach ($reflectionFunction->getParameters() as $parameter) { |
371
|
|
|
$parameterDump = ''; |
372
|
|
|
if ($parameter->isArray()) { |
373
|
|
|
if ($plainText) { |
374
|
|
|
$parameterDump .= self::ansiEscapeWrap('array ', '36', $ansiColors); |
375
|
|
|
} else { |
376
|
|
|
$parameterDump .= '<span class="extbase-debug-type">array </span>'; |
377
|
|
|
} |
378
|
|
|
} elseif ($parameter->getClass() instanceof \ReflectionClass) { |
379
|
|
|
if ($plainText) { |
380
|
|
|
$parameterDump .= self::ansiEscapeWrap($parameter->getClass()->name . ' ', '36', $ansiColors); |
381
|
|
|
} else { |
382
|
|
|
$parameterDump .= '<span class="extbase-debug-type">' |
383
|
|
|
. htmlspecialchars($parameter->getClass()->name, ENT_COMPAT) . '</span>'; |
384
|
|
|
} |
385
|
|
|
} |
386
|
|
|
if ($parameter->isPassedByReference()) { |
387
|
|
|
$parameterDump .= '&'; |
388
|
|
|
} |
389
|
|
|
if ($parameter->isVariadic()) { |
390
|
|
|
$parameterDump .= '...'; |
391
|
|
|
} |
392
|
|
|
if ($plainText) { |
393
|
|
|
$parameterDump .= self::ansiEscapeWrap('$' . $parameter->name, '37', $ansiColors); |
394
|
|
|
} else { |
395
|
|
|
$parameterDump .= '<span class="extbase-debug-property">' |
396
|
|
|
. htmlspecialchars('$' . $parameter->name, ENT_COMPAT) . '</span>'; |
397
|
|
|
} |
398
|
|
|
if ($parameter->isDefaultValueAvailable()) { |
399
|
|
|
$parameterDump .= ' = '; |
400
|
|
|
if ($plainText) { |
401
|
|
|
$parameterDump .= self::ansiEscapeWrap(var_export($parameter->getDefaultValue(), true), '33', $ansiColors); |
402
|
|
|
} else { |
403
|
|
|
$parameterDump .= '<span class="extbase-debug-string">' |
404
|
|
|
. htmlspecialchars(var_export($parameter->getDefaultValue(), true), ENT_COMPAT) . '</span>'; |
405
|
|
|
} |
406
|
|
|
} |
407
|
|
|
$params[] = $parameterDump; |
408
|
|
|
} |
409
|
|
|
$dump .= implode(', ', $params); |
410
|
|
|
if ($plainText) { |
411
|
|
|
$dump .= self::ansiEscapeWrap(') {' . PHP_EOL, '33', $ansiColors); |
412
|
|
|
} else { |
413
|
|
|
$dump .= '<span class="extbase-debug-closure">) {' . PHP_EOL . '</span>'; |
414
|
|
|
} |
415
|
|
|
$lines = (array)file((string)$reflectionFunction->getFileName()); |
416
|
|
|
for ($l = (int)$reflectionFunction->getStartLine(); $l < (int)$reflectionFunction->getEndLine() - 1; ++$l) { |
417
|
|
|
$line = (string)($lines[$l] ?? ''); |
418
|
|
|
$dump .= $plainText ? $line : htmlspecialchars($line, ENT_COMPAT); |
419
|
|
|
} |
420
|
|
|
$dump .= str_repeat(self::PLAINTEXT_INDENT, $level); |
421
|
|
|
if ($plainText) { |
422
|
|
|
$dump .= self::ansiEscapeWrap('}' . PHP_EOL, '33', $ansiColors); |
423
|
|
|
} else { |
424
|
|
|
$dump .= '<span class="extbase-debug-closure">}</span>'; |
425
|
|
|
} |
426
|
|
|
} else { |
427
|
|
|
if (get_class($object) === \stdClass::class) { |
428
|
|
|
$objReflection = new \ReflectionObject($object); |
429
|
|
|
$properties = $objReflection->getProperties(); |
430
|
|
|
} else { |
431
|
|
|
$classReflection = new \ReflectionClass(get_class($object)); |
432
|
|
|
$properties = $classReflection->getProperties(); |
433
|
|
|
} |
434
|
|
|
foreach ($properties as $property) { |
435
|
|
|
if (self::isBlacklisted($property)) { |
436
|
|
|
continue; |
437
|
|
|
} |
438
|
|
|
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level); |
439
|
|
|
if ($plainText) { |
440
|
|
|
$dump .= self::ansiEscapeWrap($property->getName(), '37', $ansiColors); |
441
|
|
|
} else { |
442
|
|
|
$dump .= '<span class="extbase-debug-property">' |
443
|
|
|
. htmlspecialchars($property->getName(), ENT_COMPAT) . '</span>'; |
444
|
|
|
} |
445
|
|
|
$dump .= ' => '; |
446
|
|
|
$property->setAccessible(true); |
447
|
|
|
$visibility = ($property->isProtected() ? 'protected' : ($property->isPrivate() ? 'private' : 'public')); |
448
|
|
|
if ($plainText) { |
449
|
|
|
$dump .= self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' '; |
450
|
|
|
} else { |
451
|
|
|
$dump .= '<span class="extbase-debug-visibility">' . $visibility . '</span>'; |
452
|
|
|
} |
453
|
|
|
$dump .= self::renderDump($property->getValue($object), $level, $plainText, $ansiColors); |
454
|
|
|
if ($object instanceof AbstractDomainObject && !$object->_isNew() && $object->_isDirty($property->getName())) { |
455
|
|
|
if ($plainText) { |
456
|
|
|
$dump .= ' ' . self::ansiEscapeWrap('modified', '43;30', $ansiColors); |
457
|
|
|
} else { |
458
|
|
|
$dump .= '<span class="extbase-debug-dirty">modified</span>'; |
459
|
|
|
} |
460
|
|
|
} |
461
|
|
|
} |
462
|
|
|
} |
463
|
|
|
} |
464
|
|
|
return $dump; |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* @param iterable $collection |
469
|
|
|
* @param int $level |
470
|
|
|
* @param bool $plainText |
471
|
|
|
* @param bool $ansiColors |
472
|
|
|
* @return string |
473
|
|
|
*/ |
474
|
|
|
protected static function renderCollection(iterable $collection, int $level, bool $plainText, bool $ansiColors): string |
475
|
|
|
{ |
476
|
|
|
$dump = ''; |
477
|
|
|
foreach ($collection as $key => $value) { |
478
|
|
|
$key = (string)$key; |
479
|
|
|
|
480
|
|
|
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level); |
481
|
|
|
if ($plainText) { |
482
|
|
|
$dump .= self::ansiEscapeWrap($key, '37', $ansiColors); |
483
|
|
|
} else { |
484
|
|
|
$dump .= '<span class="extbase-debug-property">' . htmlspecialchars($key, ENT_COMPAT) . '</span>'; |
485
|
|
|
} |
486
|
|
|
$dump .= ' => '; |
487
|
|
|
$dump .= self::renderDump($value, $level, $plainText, $ansiColors); |
488
|
|
|
} |
489
|
|
|
if ($collection instanceof \Iterator && !$collection instanceof \Generator) { |
490
|
|
|
$collection->rewind(); |
491
|
|
|
} |
492
|
|
|
return $dump; |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* Wrap a string with the ANSI escape sequence for colorful output |
497
|
|
|
* |
498
|
|
|
* @param string $string The string to wrap |
499
|
|
|
* @param string $ansiColors The ansi color sequence (e.g. "1;37") |
500
|
|
|
* @param bool $enable If FALSE, the raw string will be returned |
501
|
|
|
* @return string The wrapped or raw string |
502
|
|
|
*/ |
503
|
|
|
protected static function ansiEscapeWrap(string $string, string $ansiColors, bool $enable = true): string |
504
|
|
|
{ |
505
|
|
|
if ($enable) { |
506
|
|
|
return '[' . $ansiColors . 'm' . $string . '[0m'; |
507
|
|
|
} |
508
|
|
|
return $string; |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
/** |
512
|
|
|
* A var_dump function optimized for Extbase's object structures |
513
|
|
|
* |
514
|
|
|
* @param mixed $variable The value to dump |
515
|
|
|
* @param string $title optional custom title for the debug output |
516
|
|
|
* @param int $maxDepth Sets the max recursion depth of the dump. De- or increase the number according to your needs and memory limit. |
517
|
|
|
* @param bool $plainText If TRUE, the dump is in plain text, if FALSE the debug output is in HTML format. |
518
|
|
|
* @param bool $ansiColors If TRUE (default), ANSI color codes is added to the output, if FALSE the debug output not colored. |
519
|
|
|
* @param bool $return if TRUE, the dump is returned for custom post-processing (e.g. embed in custom HTML). If FALSE (default), the dump is directly displayed. |
520
|
|
|
* @param array $blacklistedClassNames An array of class names (RegEx) to be filtered. Default is an array of some common class names. |
521
|
|
|
* @param array $blacklistedPropertyNames An array of property names and/or array keys (RegEx) to be filtered. Default is an array of some common property names. |
522
|
|
|
* @return string if $return is TRUE, the dump is returned. By default, the dump is directly displayed, and nothing is returned. |
523
|
|
|
*/ |
524
|
|
|
public static function var_dump( |
525
|
|
|
$variable, |
526
|
|
|
string $title = null, |
527
|
|
|
int $maxDepth = 8, |
528
|
|
|
bool $plainText = false, |
529
|
|
|
bool $ansiColors = true, |
530
|
|
|
bool $return = false, |
531
|
|
|
array $blacklistedClassNames = null, |
532
|
|
|
array $blacklistedPropertyNames = null |
533
|
|
|
): string { |
534
|
|
|
self::$maxDepth = $maxDepth; |
535
|
|
|
if ($title === null) { |
536
|
|
|
$title = 'Extbase Variable Dump'; |
537
|
|
|
} |
538
|
|
|
$ansiColors = $plainText && $ansiColors; |
539
|
|
|
if ($ansiColors === true) { |
540
|
|
|
$title = '[1m' . $title . '[0m'; |
541
|
|
|
} |
542
|
|
|
$backupBlacklistedClassNames = self::$blacklistedClassNames; |
543
|
|
|
if (is_array($blacklistedClassNames)) { |
544
|
|
|
self::$blacklistedClassNames = $blacklistedClassNames; |
545
|
|
|
} |
546
|
|
|
$backupBlacklistedPropertyNames = self::$blacklistedPropertyNames; |
547
|
|
|
if (is_array($blacklistedPropertyNames)) { |
548
|
|
|
self::$blacklistedPropertyNames = $blacklistedPropertyNames; |
549
|
|
|
} |
550
|
|
|
self::clearState(); |
551
|
|
|
$css = ''; |
552
|
|
|
if (!$plainText && self::$stylesheetEchoed === false) { |
553
|
|
|
$css = ' |
554
|
|
|
<style> |
555
|
|
|
.extbase-debugger-tree{position:relative} |
556
|
|
|
.extbase-debugger-tree input{position:absolute !important;float: none !important;top:0;left:0;height:14px;width:14px;margin:0 !important;cursor:pointer;opacity:0;z-index:2} |
557
|
|
|
.extbase-debugger-tree input~.extbase-debug-content{display:none} |
558
|
|
|
.extbase-debugger-tree .extbase-debug-header:before{position:relative;top:3px;content:"";padding:0;line-height:10px;height:12px;width:12px;text-align:center;margin:0 3px 0 0;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjxyZWN0IHg9IjUiIHk9IjIiIGNsYXNzPSJzdDAiIHdpZHRoPSIxIiBoZWlnaHQ9IjciLz48L2c+PC9zdmc+);display:inline-block} |
559
|
|
|
.extbase-debugger-tree input:checked~.extbase-debug-content{display:inline} |
560
|
|
|
.extbase-debugger-tree input:checked~.extbase-debug-header:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjwvZz48L3N2Zz4=)} |
561
|
|
|
.extbase-debugger{display:block;text-align:left;background:#2a2a2a;border:1px solid #2a2a2a;box-shadow:0 3px 0 rgba(0,0,0,.5);color:#000;margin:20px;overflow:hidden;border-radius:4px} |
562
|
|
|
.extbase-debugger-floating{position:relative;z-index:99990} |
563
|
|
|
.extbase-debugger-top{background:#444;font-size:12px;font-family:monospace;color:#f1f1f1;padding:6px 15px} |
564
|
|
|
.extbase-debugger-center{padding:0 15px;margin:15px 0;background-image:repeating-linear-gradient(to bottom,transparent 0,transparent 20px,#252525 20px,#252525 40px)} |
565
|
|
|
.extbase-debugger-center,.extbase-debugger-center .extbase-debug-string,.extbase-debugger-center a,.extbase-debugger-center p,.extbase-debugger-center pre,.extbase-debugger-center strong{font-size:12px;font-weight:400;font-family:monospace;line-height:20px;color:#f1f1f1} |
566
|
|
|
.extbase-debugger-center pre{background-color:transparent;margin:0;padding:0;border:0;word-wrap:break-word;color:#999} |
567
|
|
|
.extbase-debugger-center .extbase-debug-string{color:#ce9178;white-space:normal} |
568
|
|
|
.extbase-debugger-center .extbase-debug-type{color:#569CD6;padding-right:4px} |
569
|
|
|
.extbase-debugger-center .extbase-debug-unregistered{background-color:#dce1e8} |
570
|
|
|
.extbase-debugger-center .extbase-debug-filtered,.extbase-debugger-center .extbase-debug-proxy,.extbase-debugger-center .extbase-debug-ptype,.extbase-debugger-center .extbase-debug-visibility,.extbase-debugger-center .extbase-debug-scope{color:#fff;font-size:10px;line-height:12px;padding:2px 4px;margin-right:2px;position:relative;top:-1px} |
571
|
|
|
.extbase-debugger-center .extbase-debug-scope{background-color:#497AA2} |
572
|
|
|
.extbase-debugger-center .extbase-debug-ptype{background-color:#698747} |
573
|
|
|
.extbase-debugger-center .extbase-debug-visibility{background-color:#698747} |
574
|
|
|
.extbase-debugger-center .extbase-debug-dirty{background-color:#FFFFB6} |
575
|
|
|
.extbase-debugger-center .extbase-debug-filtered{background-color:#4F4F4F} |
576
|
|
|
.extbase-debugger-center .extbase-debug-seeabove{text-decoration:none;font-style:italic} |
577
|
|
|
.extbase-debugger-center .extbase-debug-property{color:#f1f1f1} |
578
|
|
|
.extbase-debugger-center .extbase-debug-closure{color:#9BA223;} |
579
|
|
|
</style>'; |
580
|
|
|
self::$stylesheetEchoed = true; |
581
|
|
|
} |
582
|
|
|
if ($plainText) { |
583
|
|
|
$output = $title . PHP_EOL . self::renderDump($variable, 0, true, $ansiColors) . PHP_EOL . PHP_EOL; |
584
|
|
|
} else { |
585
|
|
|
$output = ' |
586
|
|
|
<div class="extbase-debugger ' . ($return ? 'extbase-debugger-inline' : 'extbase-debugger-floating') . '"> |
587
|
|
|
<div class="extbase-debugger-top">' . htmlspecialchars($title, ENT_COMPAT) . '</div> |
588
|
|
|
<div class="extbase-debugger-center"> |
589
|
|
|
<pre dir="ltr">' . self::renderDump($variable, 0, false, false) . '</pre> |
590
|
|
|
</div> |
591
|
|
|
</div> |
592
|
|
|
'; |
593
|
|
|
} |
594
|
|
|
self::$blacklistedClassNames = $backupBlacklistedClassNames; |
595
|
|
|
self::$blacklistedPropertyNames = $backupBlacklistedPropertyNames; |
596
|
|
|
if ($return === true) { |
597
|
|
|
return $css . $output; |
598
|
|
|
} |
599
|
|
|
echo $css . $output; |
600
|
|
|
|
601
|
|
|
return ''; |
602
|
|
|
} |
603
|
|
|
} |
604
|
|
|
|