TYPO3  7.6
DebuggerUtility.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Extbase\Utility;
3 
4 /* *
5  * This script belongs to the Extbase framework *
6  * *
7  * It is free software; you can redistribute it and/or modify it under *
8  * the terms of the GNU Lesser General Public License as published by the *
9  * Free Software Foundation, either version 3 of the License, or (at your *
10  * option) any later version. *
11  * *
12  * This script is distributed in the hope that it will be useful, but *
13  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
14  * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
15  * General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU Lesser General Public *
18  * License along with the script. *
19  * If not, see http://www.gnu.org/licenses/lgpl.html *
20  * *
21  * The TYPO3 project - inspiring people to share! *
22  * */
34 {
35  const PLAINTEXT_INDENT = ' ';
36  const HTML_INDENT = '&nbsp;&nbsp;&nbsp;';
37 
41  protected static $renderedObjects;
42 
48  protected static $blacklistedClassNames = array(
49  'PHPUnit_Framework_MockObject_InvocationMocker',
50  \TYPO3\CMS\Extbase\Reflection\ReflectionService::class,
51  \TYPO3\CMS\Extbase\Object\ObjectManager::class,
52  \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class,
53  \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class,
54  \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class,
55  \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class
56  );
57 
63  protected static $blacklistedPropertyNames = array('warning');
64 
70  protected static $stylesheetEchoed = false;
71 
77  protected static $maxDepth = 8;
78 
84  protected static function clearState()
85  {
86  self::$renderedObjects = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
87  }
88 
98  protected static function renderDump($value, $level, $plainText, $ansiColors)
99  {
100  $dump = '';
101  if (is_string($value)) {
102  $croppedValue = strlen($value) > 2000 ? substr($value, 0, 2000) . '...' : $value;
103  if ($plainText) {
104  $dump = self::ansiEscapeWrap(('"' . implode((PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, ($level + 1))), str_split($croppedValue, 76)) . '"'), '33', $ansiColors) . ' (' . strlen($value) . ' chars)';
105  } else {
106  $dump = sprintf('\'<span class="extbase-debug-string">%s</span>\' (%s chars)', implode('<br />' . str_repeat(self::HTML_INDENT, ($level + 1)), str_split(htmlspecialchars($croppedValue), 76)), strlen($value));
107  }
108  } elseif (is_numeric($value)) {
109  $dump = sprintf('%s (%s)', self::ansiEscapeWrap($value, '35', $ansiColors), gettype($value));
110  } elseif (is_bool($value)) {
111  $dump = $value ? self::ansiEscapeWrap('TRUE', '32', $ansiColors) : self::ansiEscapeWrap('FALSE', '32', $ansiColors);
112  } elseif (is_null($value) || is_resource($value)) {
113  $dump = gettype($value);
114  } elseif (is_array($value)) {
115  $dump = self::renderArray($value, $level + 1, $plainText, $ansiColors);
116  } elseif (is_object($value)) {
117  $dump = self::renderObject($value, $level + 1, $plainText, $ansiColors);
118  }
119  return $dump;
120  }
121 
131  protected static function renderArray($array, $level, $plainText = false, $ansiColors = false)
132  {
133  $content = '';
134  $count = count($array);
135 
136  if ($plainText) {
137  $header = self::ansiEscapeWrap('array', '36', $ansiColors);
138  } else {
139  $header = '<span class="extbase-debug-type">array</span>';
140  }
141  $header .= $count > 0 ? '(' . $count . ' item' . ($count > 1 ? 's' : '') . ')' : '(empty)';
142  if ($level >= self::$maxDepth) {
143  if ($plainText) {
144  $header .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors);
145  } else {
146  $header .= '<span class="extbase-debug-filtered">max depth</span>';
147  }
148  } else {
149  $content = self::renderCollection($array, $level, $plainText, $ansiColors);
150  if (!$plainText) {
151  $header = ($level > 1 && $count > 0 ? '<input type="checkbox" /><span class="extbase-debug-header" >' : '<span>') . $header . '</span >';
152  }
153  }
154  if ($level > 1 && $count > 0 && !$plainText) {
155  $dump = '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>';
156  } else {
157  $dump = $header . $content;
158  }
159  return $dump;
160  }
161 
171  protected static function renderObject($object, $level, $plainText = false, $ansiColors = false)
172  {
173  if ($object instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
174  $object = $object->_loadRealInstance();
175  }
176  $header = self::renderHeader($object, $level, $plainText, $ansiColors);
177  if ($level < self::$maxDepth && !self::isBlacklisted($object) && !(self::isAlreadyRendered($object) && $plainText !== true)) {
178  $content = self::renderContent($object, $level, $plainText, $ansiColors);
179  } else {
180  $content = '';
181  }
182  if ($plainText) {
183  return $header . $content;
184  } else {
185  return '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>';
186  }
187  }
188 
195  protected static function isBlacklisted($value)
196  {
197  $result = false;
198  if ($value instanceof \ReflectionProperty) {
199  $result = (strpos(implode('|', self::$blacklistedPropertyNames), $value->getName()) > 0);
200  } elseif (is_object($value)) {
201  $result = (strpos(implode('|', self::$blacklistedClassNames), get_class($value)) > 0);
202  }
203  return $result;
204  }
205 
212  protected static function isAlreadyRendered($object)
213  {
214  return self::$renderedObjects->contains($object);
215  }
216 
226  protected static function renderHeader($object, $level, $plainText, $ansiColors)
227  {
228  $dump = '';
229  $persistenceType = '';
230  $className = get_class($object);
231  $classReflection = new \ReflectionClass($className);
232  if ($plainText) {
233  $dump .= self::ansiEscapeWrap($className, '36', $ansiColors);
234  } else {
235  $dump .= '<span class="extbase-debug-type">' . $className . '</span>';
236  }
237  if ($object instanceof \TYPO3\CMS\Core\SingletonInterface) {
238  $scope = 'singleton';
239  } else {
240  $scope = 'prototype';
241  }
242  if ($plainText) {
243  $dump .= ' ' . self::ansiEscapeWrap($scope, '44;37', $ansiColors);
244  } else {
245  $dump .= $scope ? '<span class="extbase-debug-scope">' . $scope . '</span>' : '';
246  }
247  if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject) {
248  if ($object->_isDirty()) {
249  $persistenceType = 'modified';
250  } elseif ($object->_isNew()) {
251  $persistenceType = 'transient';
252  } else {
253  $persistenceType = 'persistent';
254  }
255  }
256  if ($object instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage && $object->_isDirty()) {
257  $persistenceType = 'modified';
258  }
259  if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractEntity) {
260  $domainObjectType = 'entity';
261  } elseif ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject) {
262  $domainObjectType = 'valueobject';
263  } else {
264  $domainObjectType = 'object';
265  }
266  if ($plainText) {
267  $dump .= ' ' . self::ansiEscapeWrap(($persistenceType . ' ' . $domainObjectType), '42;30', $ansiColors);
268  } else {
269  $dump .= '<span class="extbase-debug-ptype">' . ($persistenceType ? $persistenceType . ' ' : '') . $domainObjectType . '</span>';
270  }
271  if (strpos(implode('|', self::$blacklistedClassNames), get_class($object)) > 0) {
272  if ($plainText) {
273  $dump .= ' ' . self::ansiEscapeWrap('filtered', '47;30', $ansiColors);
274  } else {
275  $dump .= '<span class="extbase-debug-filtered">filtered</span>';
276  }
277  } elseif (self::$renderedObjects->contains($object) && !$plainText) {
278  $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>';
279  } elseif ($level >= self::$maxDepth && !$object instanceof \DateTime) {
280  if ($plainText) {
281  $dump .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors);
282  } else {
283  $dump .= '<span class="extbase-debug-filtered">max depth</span>';
284  }
285  } elseif ($level > 1 && !$object instanceof \DateTime && !$plainText) {
286  if (($object instanceof \Countable && empty($object)) || empty($classReflection->getProperties())) {
287  $dump = '<span>' . $dump . '</span>';
288  } else {
289  $dump = '<input type="checkbox" id="' . spl_object_hash($object) . '" /><span class="extbase-debug-header">' . $dump . '</span>';
290  }
291  }
292  if ($object instanceof \Countable) {
293  $objectCount = count($object);
294  $dump .= $objectCount > 0 ? ' (' . $objectCount . ' items)' : ' (empty)';
295  }
296  if ($object instanceof \DateTime) {
297  $dump .= ' (' . $object->format(\DateTime::RFC3339) . ', ' . $object->getTimestamp() . ')';
298  }
299  if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface && !$object->_isNew()) {
300  $dump .= ' (uid=' . $object->getUid() . ', pid=' . $object->getPid() . ')';
301  }
302  return $dump;
303  }
304 
312  protected static function renderContent($object, $level, $plainText, $ansiColors)
313  {
314  $dump = '';
315  if ($object instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage || $object instanceof \Iterator || $object instanceof \ArrayObject) {
316  $dump .= self::renderCollection($object, $level, $plainText, $ansiColors);
317  } else {
318  self::$renderedObjects->attach($object);
319  if (!$plainText) {
320  $dump .= '<a name="' . spl_object_hash($object) . '" id="' . spl_object_hash($object) . '"></a>';
321  }
322  if (get_class($object) === 'stdClass') {
323  $objReflection = new \ReflectionObject($object);
324  $properties = $objReflection->getProperties();
325  } else {
326  $classReflection = new \ReflectionClass(get_class($object));
327  $properties = $classReflection->getProperties();
328  }
329  foreach ($properties as $property) {
330  if (self::isBlacklisted($property)) {
331  continue;
332  }
333  $dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level) . ($plainText ? '' : '<span class="extbase-debug-property">') . self::ansiEscapeWrap($property->getName(), '37', $ansiColors) . ($plainText ? '' : '</span>') . ' => ';
334  $property->setAccessible(true);
335  $dump .= self::renderDump($property->getValue($object), $level, $plainText, $ansiColors);
336  if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject && !$object->_isNew() && $object->_isDirty($property->getName())) {
337  if ($plainText) {
338  $dump .= ' ' . self::ansiEscapeWrap('modified', '43;30', $ansiColors);
339  } else {
340  $dump .= '<span class="extbase-debug-dirty">modified</span>';
341  }
342  }
343  }
344  }
345  return $dump;
346  }
347 
355  protected static function renderCollection($collection, $level, $plainText, $ansiColors)
356  {
357  $dump = '';
358  foreach ($collection as $key => $value) {
359  $dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level) . ($plainText ? '' : '<span class="extbase-debug-property">') . self::ansiEscapeWrap($key, '37', $ansiColors) . ($plainText ? '' : '</span>') . ' => ';
360  $dump .= self::renderDump($value, $level, $plainText, $ansiColors);
361  }
362  if ($collection instanceof \Iterator) {
363  $collection->rewind();
364  }
365  return $dump;
366  }
367 
376  protected static function ansiEscapeWrap($string, $ansiColors, $enable = true)
377  {
378  if ($enable) {
379  return '[' . $ansiColors . 'm' . $string . '';
380  } else {
381  return $string;
382  }
383  }
384 
399  public static function var_dump($variable, $title = null, $maxDepth = 8, $plainText = false, $ansiColors = true, $return = false, $blacklistedClassNames = null, $blacklistedPropertyNames = null)
400  {
401  self::$maxDepth = $maxDepth;
402  if ($title === null) {
403  $title = 'Extbase Variable Dump';
404  }
405  $ansiColors = $plainText && $ansiColors;
406  if ($ansiColors === true) {
407  $title = '' . $title . '';
408  }
409  if (is_array($blacklistedClassNames)) {
410  self::$blacklistedClassNames = $blacklistedClassNames;
411  }
412  if (is_array($blacklistedPropertyNames)) {
413  self::$blacklistedPropertyNames = $blacklistedPropertyNames;
414  }
415  self::clearState();
416  if (!$plainText && self::$stylesheetEchoed === false) {
417  echo '
418  <style type=\'text/css\'>
419  .extbase-debugger-tree{position:relative}
420  .extbase-debugger-tree input{position:absolute;top:0;left:0;height:14px;width:14px;margin:0;cursor:pointer;opacity:0;z-index:2}
421  .extbase-debugger-tree input~.extbase-debug-content{display:none}
422  .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}
423  .extbase-debugger-tree input:checked~.extbase-debug-content{display:inline}
424  .extbase-debugger-tree input:checked~.extbase-debug-header:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjwvZz48L3N2Zz4=)}
425  .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}
426  .extbase-debugger-floating{position:relative;z-index:999}
427  .extbase-debugger-top{background:#444;font-size:12px;font-family:monospace;color:#f1f1f1;padding:6px 15px}
428  .extbase-debugger-center{padding:0 15px;margin:15px 0;background-image:repeating-linear-gradient(to bottom,transparent 0,transparent 20px,#252525 20px,#252525 40px)}
429  .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}
430  .extbase-debugger-center pre{background-color:transparent;margin:0;padding:0;border:0;word-wrap:break-word;color:#999}
431  .extbase-debugger-center .extbase-debug-string{color:#ce9178;white-space:normal}
432  .extbase-debugger-center .extbase-debug-type{color:#569CD6;padding-right:4px}
433  .extbase-debugger-center .extbase-debug-unregistered{background-color:#dce1e8}
434  .extbase-debugger-center .extbase-debug-filtered,.extbase-debugger-center .extbase-debug-proxy,.extbase-debugger-center .extbase-debug-ptype,.extbase-debugger-center .extbase-debug-scope{color:#fff;font-size:10px;line-height:12px;padding:2px 4px;margin-right:2px;position:relative;top:-1px}
435  .extbase-debugger-center .extbase-debug-scope{background-color:#497AA2}
436  .extbase-debugger-center .extbase-debug-ptype{background-color:#698747}
437  .extbase-debugger-center .extbase-debug-dirty{background-color:#FFFFB6}
438  .extbase-debugger-center .extbase-debug-filtered{background-color:#4F4F4F}
439  .extbase-debugger-center .extbase-debug-seeabove{text-decoration:none;font-style:italic}
440  .extbase-debugger-center .extbase-debug-property{color:#f1f1f1}
441  </style>';
442  self::$stylesheetEchoed = true;
443  }
444  if ($plainText) {
445  $output = $title . PHP_EOL . self::renderDump($variable, 0, true, $ansiColors) . PHP_EOL . PHP_EOL;
446  } else {
447  $output = '
448  <div class="extbase-debugger ' . ($return ? 'extbase-debugger-inline' : 'extbase-debugger-floating') . '">
449  <div class="extbase-debugger-top">' . htmlspecialchars($title) . '</div>
450  <div class="extbase-debugger-center">
451  <pre dir="ltr">' . self::renderDump($variable, 0, false, false) . '</pre>
452  </div>
453  </div>
454  ';
455  }
456  if ($return === true) {
457  return $output;
458  } else {
459  echo $output;
460  }
461  return '';
462  }
463 }