PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

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