PageRenderTime 100ms CodeModel.GetById 40ms app.highlight 27ms RepoModel.GetById 13ms app.codeStats 1ms

/Classes/TYPO3/FLOW3/Error/Debugger.php

https://github.com/christianjul/FLOW3-Composer
PHP | 495 lines | 351 code | 40 blank | 104 comment | 93 complexity | 5ae450e8bbfd18183d4386c413a0aa90 MD5 | raw file
  1<?php
  2namespace TYPO3\FLOW3\Error;
  3
  4/*                                                                        *
  5 * This script belongs to the FLOW3 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, either version 3   *
  9 * of the License, or (at your option) any later version.                 *
 10 *                                                                        *
 11 * The TYPO3 project - inspiring people to share!                         *
 12 *                                                                        */
 13
 14use TYPO3\FLOW3\Annotations as FLOW3;
 15use TYPO3\FLOW3\Reflection\ObjectAccess;
 16
 17/**
 18 * A debugging utility class
 19 *
 20 * @FLOW3\Proxy(false)
 21 */
 22class Debugger {
 23
 24	/**
 25	 * @var \TYPO3\FLOW3\Object\ObjectManagerInterface
 26	 */
 27	static protected $objectManager;
 28
 29	/**
 30	 *
 31	 * @var array
 32	 */
 33	static protected $renderedObjects = array();
 34
 35	/**
 36	 * Hardcoded list of FLOW3 class names (regex) which should not be displayed during debugging
 37	 * @var array
 38	 */
 39	static protected $blacklistedClassNames = '/
 40		(TYPO3\\\\FLOW3\\\\Aop.*)
 41		(TYPO3\\\\FLOW3\\\\Cac.*) |
 42		(TYPO3\\\\FLOW3\\\\Con.*) |
 43		(TYPO3\\\\FLOW3\\\\Uti.*) |
 44		(TYPO3\\\\FLOW3\\\\Mvc\\\\Routing.*) |
 45		(TYPO3\\\\FLOW3\\\\Log.*) |
 46		(TYPO3\\\\FLOW3\\\\Obj.*) |
 47		(TYPO3\\\\FLOW3\\\\Pac.*) |
 48		(TYPO3\\\\FLOW3\\\\Persistence\\\\(?!Doctrine\\\\Mapping).*) |
 49		(TYPO3\\\\FLOW3\\\\Pro.*) |
 50		(TYPO3\\\\FLOW3\\\\Ref.*) |
 51		(TYPO3\\\\FLOW3\\\\Sec.*) |
 52		(TYPO3\\\\Fluid\\\\.*) |
 53		(PHPUnit_Framework_MockObject_InvocationMocker)
 54		/xs';
 55
 56	static protected $blacklistedPropertyNames = '/
 57		(FLOW3_Aop_.*)
 58		/xs';
 59
 60	/**
 61	 * Is set to TRUE once the CSS file is included in the current page to prevent double inclusions of the CSS file.
 62	 * @var boolean
 63	 */
 64	static public $stylesheetEchoed = FALSE;
 65
 66	/**
 67	 * Injects the Object Manager
 68	 *
 69	 * @param \TYPO3\FLOW3\Object\ObjectManagerInterface $objectManager
 70	 * @return void
 71	 */
 72	static public function injectObjectManager(\TYPO3\FLOW3\Object\ObjectManagerInterface $objectManager) {
 73		self::$objectManager = $objectManager;
 74	}
 75
 76	/**
 77	 * Clear the state of the debugger
 78	 *
 79	 * @return void
 80	 */
 81	static public function clearState() {
 82		self::$renderedObjects = array();
 83	}
 84
 85	/**
 86	 * Renders a dump of the given variable
 87	 *
 88	 * @param mixed $variable
 89	 * @param integer $level
 90	 * @param boolean $plaintext
 91	 * @param boolean $ansiColors
 92	 * @return string
 93	 */
 94	static public function renderDump($variable, $level, $plaintext = FALSE, $ansiColors = FALSE) {
 95		if ($level > 50) {
 96			return 'RECURSION ... ' . chr(10);
 97		}
 98		if (is_string($variable)) {
 99			$croppedValue = (strlen($variable) > 2000) ? substr($variable, 0, 2000) . '…' : $variable;
100			if ($plaintext) {
101				$dump = 'string ' . self::ansiEscapeWrap('"' . $croppedValue . '"', '33', $ansiColors) . ' (' . strlen($variable) . ')';
102			} else {
103				$dump = sprintf('\'<span class="debug-string">%s</span>\' (%s)', htmlspecialchars($croppedValue), strlen($variable));
104			}
105		} elseif (is_numeric($variable)) {
106			$dump = sprintf('%s %s', gettype($variable), self::ansiEscapeWrap($variable, '35', $ansiColors));
107		} elseif (is_array($variable)) {
108			$dump = \TYPO3\FLOW3\Error\Debugger::renderArrayDump($variable, $level + 1, $plaintext, $ansiColors);
109		} elseif (is_object($variable)) {
110			$dump = \TYPO3\FLOW3\Error\Debugger::renderObjectDump($variable, $level + 1, TRUE, $plaintext, $ansiColors);
111		} elseif (is_bool($variable)) {
112			$dump = $variable ? self::ansiEscapeWrap('TRUE', '32', $ansiColors) : self::ansiEscapeWrap('FALSE', '31', $ansiColors);
113		} elseif (is_null($variable) || is_resource($variable)) {
114			$dump = gettype($variable);
115		} else {
116			$dump = '[unhandled type]';
117		}
118		return $dump;
119	}
120
121	/**
122	 * Renders a dump of the given array
123	 *
124	 * @param array $array
125	 * @param integer $level
126	 * @param boolean $plaintext
127	 * @param boolean $ansiColors
128	 * @return string
129	 */
130	static protected function renderArrayDump($array, $level, $plaintext = FALSE, $ansiColors = FALSE) {
131		$type = is_array($array) ? 'array' : get_class($array);
132		$dump = $type . (count($array) ? '(' . count($array) .')' : '(empty)');
133		foreach ($array as $key => $value) {
134			$dump .= chr(10) . str_repeat(' ', $level) . self::renderDump($key, 0, $plaintext, $ansiColors) . ' => ';
135			$dump .= self::renderDump($value, $level + 1, $plaintext, $ansiColors);
136		}
137		return $dump;
138	}
139
140	/**
141	 * Renders a dump of the given object
142	 *
143	 * @param object $object
144	 * @param integer $level
145	 * @param boolean $renderProperties
146	 * @param boolean $plaintext
147	 * @param boolean $ansiColors
148	 * @return string
149	 */
150	static protected function renderObjectDump($object, $level, $renderProperties = TRUE, $plaintext = FALSE, $ansiColors = FALSE) {
151		$dump = '';
152		$scope = '';
153		$additionalAttributes = '';
154
155		if ($object instanceof \Doctrine\Common\Collections\Collection) {
156			return self::renderArrayDump(\Doctrine\Common\Util\Debug::export($object, 12), $level, $plaintext, $ansiColors);
157		}
158
159			// Objects returned from Doctrine's Debug::export function are stdClass with special properties:
160		try {
161			$objectIdentifier = ObjectAccess::getProperty($object, 'FLOW3_Persistence_Identifier', TRUE);
162		} catch (\TYPO3\FLOW3\Reflection\Exception\PropertyNotAccessibleException $exception) {
163			$objectIdentifier = spl_object_hash($object);
164		}
165		$className = ($object instanceof \stdClass && isset($object->__CLASS__)) ? $object->__CLASS__ : get_class($object);
166
167		if (preg_match(self::$blacklistedClassNames, $className) !== 0 || isset(self::$renderedObjects[$objectIdentifier])) {
168			$renderProperties = FALSE;
169		}
170		self::$renderedObjects[$objectIdentifier] = TRUE;
171
172		if (self::$objectManager !== NULL) {
173			$objectName = self::$objectManager->getObjectNameByClassName(get_class($object));
174			if ($objectName !== FALSE) {
175				switch(self::$objectManager->getScope($objectName)) {
176					case \TYPO3\FLOW3\Object\Configuration\Configuration::SCOPE_PROTOTYPE :
177						$scope = 'prototype';
178						break;
179					case \TYPO3\FLOW3\Object\Configuration\Configuration::SCOPE_SINGLETON :
180						$scope = 'singleton';
181						break;
182					case \TYPO3\FLOW3\Object\Configuration\Configuration::SCOPE_SESSION :
183						$scope = 'session';
184						break;
185				}
186			} else {
187				$additionalAttributes .= ' debug-unregistered';
188			}
189		}
190
191		if ($renderProperties === TRUE && !$plaintext) {
192			if ($scope === '') {
193				$scope = 'prototype';
194			}
195			$scope .= '<a id="o' . $objectIdentifier . '"></a>';
196		}
197
198		if ($plaintext) {
199			$dump .= $className;
200			$dump .= ($scope !== '') ? ' ' . self::ansiEscapeWrap($scope, '44;37', $ansiColors) : '';
201		} else {
202			$dump .= '<span class="debug-object' . $additionalAttributes . '" title="' . $objectIdentifier . '">' . $className . '</span>';
203			$dump .= ($scope !== '') ? '<span class="debug-scope">' . $scope .'</span>' : '';
204		}
205
206		if (property_exists($object, 'FLOW3_Persistence_Identifier')) {
207			$persistenceIdentifier = $objectIdentifier;
208			$persistenceType = 'persistable';
209		} elseif ($object instanceof \Closure) {
210			$persistenceIdentifier = 'n/a';
211			$persistenceType = 'closure';
212		} else {
213			$persistenceIdentifier = 'unknown';
214			$persistenceType = 'object';
215		}
216
217		if ($plaintext) {
218			$dump .= ' ' . self::ansiEscapeWrap($persistenceType, '42;37', $ansiColors);
219		} else {
220			$dump .= '<span class="debug-ptype" title="' . $persistenceIdentifier . '">' . $persistenceType . '</span>';
221		}
222
223		if ($object instanceof \TYPO3\FLOW3\Object\Proxy\ProxyInterface || (property_exists($object, '__IS_PROXY__') && $object->__IS_PROXY__ === TRUE)) {
224			if ($plaintext) {
225				$dump .= ' ' . self::ansiEscapeWrap('proxy', '41;37', $ansiColors);
226			} else {
227				$dump .= '<span class="debug-proxy" title="' . $className . '">proxy</span>';
228			}
229		}
230
231		if ($renderProperties === TRUE) {
232			if ($object instanceof \SplObjectStorage) {
233				$dump .= ' (' . (count($object) ?: 'empty') . ')';
234				foreach ($object as $value) {
235					$dump .= chr(10);
236					$dump .= str_repeat(' ', $level);
237					$dump .= self::renderObjectDump($value, 0, FALSE, $plaintext, $ansiColors);
238				}
239			} else {
240				$classReflection = new \ReflectionClass($className);
241				$properties = $classReflection->getProperties();
242				foreach ($properties as $property) {
243					if (preg_match(self::$blacklistedPropertyNames, $property->getName())) {
244						continue;
245					}
246					$dump .= chr(10);
247					$dump .= str_repeat(' ', $level) . ($plaintext ? '' : '<span class="debug-property">') . self::ansiEscapeWrap($property->getName(), '36', $ansiColors) . ($plaintext ? '' : '</span>') . ' => ';
248					$property->setAccessible(TRUE);
249					$value = $property->getValue($object);
250					if (is_array($value)) {
251						$dump .= self::renderDump($value, $level + 1, $plaintext, $ansiColors);
252					} elseif (is_object($value)) {
253						$dump .= self::renderObjectDump($value, $level + 1, TRUE, $plaintext, $ansiColors);
254					} else {
255						$dump .= self::renderDump($value, $level, $plaintext, $ansiColors);
256					}
257				}
258			}
259		} elseif (isset(self::$renderedObjects[$objectIdentifier])) {
260			if (!$plaintext) {
261				$dump = '<a href="#o' . $objectIdentifier . '" onclick="document.location.hash=\'#o' . $objectIdentifier . '\'; return false;" class="debug-seeabove" title="see above">' . $dump . '</a>';
262			}
263		}
264		return $dump;
265	}
266
267	/**
268	 * Renders some backtrace
269	 *
270	 * @param array $trace The trace
271	 * @param boolean $includeCode Include code snippet
272	 * @param boolean $plaintext
273	 * @return string Backtrace information
274	 */
275	static public function getBacktraceCode(array $trace, $includeCode = TRUE, $plaintext = FALSE) {
276		$backtraceCode = '';
277		if (count($trace)) {
278			foreach ($trace as $index => $step) {
279				if ($plaintext) {
280					$class = isset($step['class']) ? $step['class'] . '::' : '';
281				} else {
282					$class = isset($step['class']) ? $step['class'] . '<span style="color:white;">::</span>' : '';
283				}
284
285				$arguments = '';
286				if (isset($step['args']) && is_array($step['args'])) {
287					foreach ($step['args'] as $argument) {
288						if ($plaintext) {
289							$arguments .= (strlen($arguments) === 0) ? '' : ', ';
290						} else {
291							$arguments .= (strlen($arguments) === 0) ? '' : '<span style="color:white;">,</span> ';
292						}
293						if (is_object($argument)) {
294							if ($plaintext) {
295								$arguments .= get_class($argument);
296							} else {
297								$arguments .= '<span style="color:#FF8700;"><em>' . get_class($argument) . '</em></span>';
298							}
299						} elseif (is_string($argument)) {
300							$preparedArgument = (strlen($argument) < 100) ? $argument : substr($argument, 0, 50) . '…' . substr($argument, -50);
301							$preparedArgument = htmlspecialchars($preparedArgument);
302							if ($plaintext) {
303								$arguments .= '"' . $argument . '"';
304							} else {
305								$preparedArgument = str_replace("…", '<span style="color:white;">…</span>', $preparedArgument);
306								$preparedArgument = str_replace("\n", '<span style="color:white;">⏎</span>', $preparedArgument);
307								$arguments .= '"<span style="color:#FF8700;" title="' . htmlspecialchars($argument) . '">' . $preparedArgument . '</span>"';
308							}
309						} elseif (is_numeric($argument)) {
310							if ($plaintext) {
311								$arguments .= (string)$argument;
312							} else {
313								$arguments .= '<span style="color:#FF8700;">' . (string)$argument . '</span>';
314							}
315						} elseif (is_bool($argument)) {
316							if ($plaintext) {
317								$arguments .= ($argument === TRUE ? 'TRUE' : 'FALSE');
318							} else {
319								$arguments .= '<span style="color:#FF8700;">' . ($argument === TRUE ? 'TRUE' : 'FALSE') . '</span>';
320							}
321						} elseif (is_array($argument)) {
322							if ($plaintext) {
323								$arguments .= 'array|' . count($argument) . '|';
324							} else {
325								$arguments .= '<span style="color:#FF8700;" title="%s"><em>array|' . count($argument) . '|</em></span>';
326							}
327						} else {
328							if ($plaintext) {
329								$arguments .= gettype($argument);
330							} else {
331								$arguments .= '<span style="color:#FF8700;"><em>' . gettype($argument) . '</em></span>';
332							}
333						}
334					}
335				}
336
337				if ($plaintext) {
338					$backtraceCode .= (count($trace) - $index) . ' ' . $class . $step['function'] . '(' . $arguments . ')';
339				} else {
340					$backtraceCode .= '<pre style="color:#69A550; background-color: #414141; padding: 4px 2px 4px 2px;">';
341					$backtraceCode .= '<span style="color:white;">' . (count($trace) - $index) . '</span> ' . $class . $step['function'] . '<span style="color:white;">(' . $arguments . ')</span>';
342					$backtraceCode .= '</pre>';
343				}
344
345				if (isset($step['file']) && $includeCode) {
346					$backtraceCode .= self::getCodeSnippet($step['file'], $step['line'], $plaintext);
347				}
348				if ($plaintext) {
349					$backtraceCode .= PHP_EOL;
350				} else {
351					$backtraceCode .= '<br />';
352				}
353			}
354		}
355
356		return $backtraceCode;
357	}
358
359	/**
360	 * Returns a code snippet from the specified file.
361	 *
362	 * @param string $filePathAndName Absolute path and filename of the PHP file
363	 * @param integer $lineNumber Line number defining the center of the code snippet
364	 * @param boolean $plaintext
365	 * @return string The code snippet
366	 * @todo make plaintext-aware
367	 */
368	static public function getCodeSnippet($filePathAndName, $lineNumber, $plaintext = FALSE) {
369		$pathPosition = strpos($filePathAndName, 'Packages/');
370		if ($plaintext) {
371			$codeSnippet = PHP_EOL;
372		} else {
373			$codeSnippet = '<br />';
374		}
375		if (@file_exists($filePathAndName)) {
376			$phpFile = @file($filePathAndName);
377			if (is_array($phpFile)) {
378				$startLine = ($lineNumber > 2) ? ($lineNumber - 2) : 1;
379				$endLine = ($lineNumber < (count($phpFile) - 2)) ? ($lineNumber + 3) : count($phpFile) + 1;
380				if ($endLine > $startLine) {
381					if ($pathPosition !== FALSE) {
382						if ($plaintext) {
383							$codeSnippet = PHP_EOL . substr($filePathAndName, $pathPosition) . ':' . PHP_EOL;
384						} else {
385							$codeSnippet = '<br /><span style="font-size:10px;">' . substr($filePathAndName, $pathPosition) . ':</span><br /><pre>';
386						}
387					} else {
388						if ($plaintext) {
389							$codeSnippet = PHP_EOL . $filePathAndName . ':' . PHP_EOL;
390						} else {
391							$codeSnippet = '<br /><span style="font-size:10px;">' . $filePathAndName . ':</span><br /><pre>';
392						}
393					}
394					for ($line = $startLine; $line < $endLine; $line++) {
395						$codeLine = str_replace("\t", ' ', $phpFile[$line-1]);
396
397						if ($line === $lineNumber) {
398							if (!$plaintext) {
399								$codeSnippet .= '</pre><pre style="background-color: #F1F1F1; color: black;">';
400							}
401						}
402						$codeSnippet .= sprintf('%05d', $line) . ': ';
403
404						if ($plaintext) {
405							$codeSnippet .= $codeLine;
406						} else {
407							$codeSnippet .= htmlspecialchars($codeLine);
408						}
409
410						if ($line === $lineNumber && !$plaintext) {
411							$codeSnippet .= '</pre><pre>';
412						}
413					}
414					if (!$plaintext) {
415						$codeSnippet .= '</pre>';
416					}
417				}
418			}
419		}
420		return $codeSnippet;
421	}
422
423	/**
424	 * Wrap a string with the ANSI escape sequence for colorful output
425	 *
426	 * @param string $string The string to wrap
427	 * @param string $ansiColors The ansi color sequence (e.g. "1;37")
428	 * @param boolean $enable If FALSE, the raw string will be returned
429	 * @return string The wrapped or raw string
430	 */
431	static protected function ansiEscapeWrap($string, $ansiColors, $enable = TRUE) {
432		if ($enable) {
433			return "\x1B[" . $ansiColors . 'm' . $string . "\x1B[0m";
434		} else {
435			return $string;
436		}
437	}
438}
439
440namespace TYPO3\FLOW3;
441
442/**
443 * A var_dump function optimized for FLOW3's object structures
444 *
445 * @param mixed $variable The variable to display a dump of
446 * @param string $title optional custom title for the debug output
447 * @param boolean $return if TRUE, the dump is returned for displaying it embedded in custom HTML. If FALSE (default), the variable dump is directly displayed.
448 * @param boolean $plaintext If TRUE, the dump is in plain text, if FALSE the debug output is in HTML format. If not specified, the mode is guessed from FLOW3_SAPITYPE
449 * @return void|string if $return is TRUE, the variable dump is returned. By default, the dump is directly displayed, and nothing is returned.
450 * @api
451 */
452function var_dump($variable, $title = NULL, $return = FALSE, $plaintext = NULL) {
453	if ($plaintext === NULL) {
454		$plaintext = (FLOW3_SAPITYPE === 'CLI');
455		$ansiColors = $plaintext && DIRECTORY_SEPARATOR === '/';
456	} else {
457		$ansiColors = FALSE;
458	}
459
460	if ($title === NULL) {
461		$title = 'FLOW3 Variable Dump';
462	}
463	if ($ansiColors) {
464		$title = "\x1B[1m" . $title . "\x1B[0m";
465	}
466	\TYPO3\FLOW3\Error\Debugger::clearState();
467
468	if (!$plaintext && \TYPO3\FLOW3\Error\Debugger::$stylesheetEchoed === FALSE) {
469		echo '<link rel="stylesheet" type="text/css" href="/_Resources/Static/Packages/TYPO3.FLOW3/Error/Debugger.css" />';
470		\TYPO3\FLOW3\Error\Debugger::$stylesheetEchoed = TRUE;
471	}
472
473	if ($plaintext) {
474		$output = $title . chr(10) . \TYPO3\FLOW3\Error\Debugger::renderDump($variable, 0, TRUE, $ansiColors) . chr(10) . chr(10);
475	} else {
476		$output = '
477			<div class="FLOW3-Error-Debugger-VarDump ' . ($return ? 'FLOW3-Error-Debugger-VarDump-Inline' : 'FLOW3-Error-Debugger-VarDump-Floating') . '">
478				<div class="FLOW3-Error-Debugger-VarDump-Top">
479					' . htmlspecialchars($title) . '
480				</div>
481				<div class="FLOW3-Error-Debugger-VarDump-Center">
482					<pre dir="ltr">' . \TYPO3\FLOW3\Error\Debugger::renderDump($variable, 0, FALSE, FALSE) . '</pre>
483				</div>
484			</div>
485		';
486	}
487
488	if ($return === TRUE) {
489		return $output;
490	} else {
491		echo $output;
492	}
493}
494
495?>