PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/cake/libs/debugger.php

https://github.com/mariuz/firetube
PHP | 568 lines | 364 code | 28 blank | 176 comment | 79 complexity | 916820bc0e3e55713acc89aef443e3f6 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /* SVN FILE: $Id$ */
  3. /**
  4. * Framework debugging and PHP error-handling class
  5. *
  6. * Provides enhanced logging, stack traces, and rendering debug views
  7. *
  8. * PHP versions 4 and 5
  9. *
  10. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  11. * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  12. *
  13. * Licensed under The MIT License
  14. * Redistributions of files must retain the above copyright notice.
  15. *
  16. * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  17. * @link http://cakephp.org CakePHP(tm) Project
  18. * @package cake
  19. * @subpackage cake.cake.libs
  20. * @since CakePHP(tm) v 1.2.4560
  21. * @version $Revision$
  22. * @modifiedby $LastChangedBy$
  23. * @lastmodified $Date$
  24. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  25. */
  26. /**
  27. * Included libraries.
  28. *
  29. */
  30. if (!class_exists('Object')) {
  31. uses('object');
  32. }
  33. if (!class_exists('CakeLog')) {
  34. uses('cake_log');
  35. }
  36. /**
  37. * Provide custom logging and error handling.
  38. *
  39. * Debugger overrides PHP's default error handling to provide stack traces and enhanced logging
  40. *
  41. * @package cake
  42. * @subpackage cake.cake.libs
  43. * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
  44. */
  45. class Debugger extends Object {
  46. /**
  47. * A list of errors generated by the application.
  48. *
  49. * @var array
  50. * @access public
  51. */
  52. var $errors = array();
  53. /**
  54. * Contains the base URL for error code documentation.
  55. *
  56. * @var string
  57. * @access public
  58. */
  59. var $helpPath = null;
  60. /**
  61. * The current output format.
  62. *
  63. * @var string
  64. * @access protected
  65. */
  66. var $_outputFormat = 'js';
  67. /**
  68. * Holds current output data when outputFormat is false.
  69. *
  70. * @var string
  71. * @access private
  72. */
  73. var $__data = array();
  74. /**
  75. * Constructor.
  76. *
  77. */
  78. function __construct() {
  79. $docRef = ini_get('docref_root');
  80. if (empty($docRef)) {
  81. ini_set('docref_root', 'http://php.net/');
  82. }
  83. if (!defined('E_RECOVERABLE_ERROR')) {
  84. define('E_RECOVERABLE_ERROR', 4096);
  85. }
  86. }
  87. /**
  88. * Returns a reference to the Debugger singleton object instance.
  89. *
  90. * @return object
  91. * @access public
  92. * @static
  93. */
  94. function &getInstance($class = null) {
  95. static $instance = array();
  96. if (!empty($class)) {
  97. if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) {
  98. $instance[0] = & new $class();
  99. if (Configure::read() > 0) {
  100. Configure::version(); // Make sure the core config is loaded
  101. $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath');
  102. }
  103. }
  104. }
  105. if (!$instance) {
  106. $instance[0] =& new Debugger();
  107. if (Configure::read() > 0) {
  108. Configure::version(); // Make sure the core config is loaded
  109. $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath');
  110. }
  111. }
  112. return $instance[0];
  113. }
  114. /**
  115. * Formats and outputs the contents of the supplied variable.
  116. *
  117. * @param $var mixed the variable to dump
  118. * @return void
  119. * @see exportVar
  120. * @access public
  121. * @static
  122. * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
  123. */
  124. function dump($var) {
  125. $_this = Debugger::getInstance();
  126. pr($_this->exportVar($var));
  127. }
  128. /**
  129. * Creates a detailed stack trace log at the time of invocation, much like dump()
  130. * but to debug.log.
  131. *
  132. * @param $var mixed Variable or content to log
  133. * @param $level int type of log to use. Defaults to LOG_DEBUG
  134. * @return void
  135. * @static
  136. * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
  137. */
  138. function log($var, $level = LOG_DEBUG) {
  139. $_this = Debugger::getInstance();
  140. $source = $_this->trace(array('start' => 1)) . "\n";
  141. CakeLog::write($level, "\n" . $source . $_this->exportVar($var));
  142. }
  143. /**
  144. * Overrides PHP's default error handling.
  145. *
  146. * @param integer $code Code of error
  147. * @param string $description Error description
  148. * @param string $file File on which error occurred
  149. * @param integer $line Line that triggered the error
  150. * @param array $context Context
  151. * @return boolean true if error was handled
  152. * @access public
  153. */
  154. function handleError($code, $description, $file = null, $line = null, $context = null) {
  155. if (error_reporting() == 0 || $code === 2048 || $code === 8192) {
  156. return;
  157. }
  158. $_this = Debugger::getInstance();
  159. if (empty($file)) {
  160. $file = '[internal]';
  161. }
  162. if (empty($line)) {
  163. $line = '??';
  164. }
  165. $file = $_this->trimPath($file);
  166. $info = compact('code', 'description', 'file', 'line');
  167. if (!in_array($info, $_this->errors)) {
  168. $_this->errors[] = $info;
  169. } else {
  170. return;
  171. }
  172. $level = LOG_DEBUG;
  173. switch ($code) {
  174. case E_PARSE:
  175. case E_ERROR:
  176. case E_CORE_ERROR:
  177. case E_COMPILE_ERROR:
  178. case E_USER_ERROR:
  179. $error = 'Fatal Error';
  180. $level = LOG_ERROR;
  181. break;
  182. case E_WARNING:
  183. case E_USER_WARNING:
  184. case E_COMPILE_WARNING:
  185. case E_RECOVERABLE_ERROR:
  186. $error = 'Warning';
  187. $level = LOG_WARNING;
  188. break;
  189. case E_NOTICE:
  190. case E_USER_NOTICE:
  191. $error = 'Notice';
  192. $level = LOG_NOTICE;
  193. break;
  194. default:
  195. return false;
  196. break;
  197. }
  198. $helpCode = null;
  199. if (!empty($_this->helpPath) && preg_match('/.*\[([0-9]+)\]$/', $description, $codes)) {
  200. if (isset($codes[1])) {
  201. $helpCode = $codes[1];
  202. $description = trim(preg_replace('/\[[0-9]+\]$/', '', $description));
  203. }
  204. }
  205. echo $_this->_output($level, $error, $code, $helpCode, $description, $file, $line, $context);
  206. if (Configure::read('log')) {
  207. CakeLog::write($level, "{$error} ({$code}): {$description} in [{$file}, line {$line}]");
  208. }
  209. if ($error == 'Fatal Error') {
  210. exit();
  211. }
  212. return true;
  213. }
  214. /**
  215. * Outputs a stack trace based on the supplied options.
  216. *
  217. * @param array $options Format for outputting stack trace
  218. * @return string Formatted stack trace
  219. * @access public
  220. * @static
  221. * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
  222. */
  223. function trace($options = array()) {
  224. $options = array_merge(array(
  225. 'depth' => 999,
  226. 'format' => '',
  227. 'args' => false,
  228. 'start' => 0,
  229. 'scope' => null,
  230. 'exclude' => null
  231. ),
  232. $options
  233. );
  234. $backtrace = debug_backtrace();
  235. $back = array();
  236. $count = count($backtrace);
  237. for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
  238. $trace = array_merge(
  239. array(
  240. 'file' => '[internal]',
  241. 'line' => '??'
  242. ),
  243. $backtrace[$i]
  244. );
  245. if (isset($backtrace[$i + 1])) {
  246. $next = array_merge(
  247. array(
  248. 'line' => '??',
  249. 'file' => '[internal]',
  250. 'class' => null,
  251. 'function' => '[main]'
  252. ),
  253. $backtrace[$i + 1]
  254. );
  255. $function = $next['function'];
  256. if (!empty($next['class'])) {
  257. $function = $next['class'] . '::' . $function . '(';
  258. if ($options['args'] && isset($next['args'])) {
  259. $args = array();
  260. foreach ($next['args'] as $arg) {
  261. $args[] = Debugger::exportVar($arg);
  262. }
  263. $function .= implode(', ', $args);
  264. }
  265. $function .= ')';
  266. }
  267. } else {
  268. $function = '[main]';
  269. }
  270. if (in_array($function, array('call_user_func_array', 'trigger_error'))) {
  271. continue;
  272. }
  273. if ($options['format'] == 'points' && $trace['file'] != '[internal]') {
  274. $back[] = array('file' => $trace['file'], 'line' => $trace['line']);
  275. } elseif (empty($options['format'])) {
  276. $back[] = $function . ' - ' . Debugger::trimPath($trace['file']) . ', line ' . $trace['line'];
  277. } else {
  278. $back[] = $trace;
  279. }
  280. }
  281. if ($options['format'] == 'array' || $options['format'] == 'points') {
  282. return $back;
  283. }
  284. return implode("\n", $back);
  285. }
  286. /**
  287. * Shortens file paths by replacing the application base path with 'APP', and the CakePHP core
  288. * path with 'CORE'.
  289. *
  290. * @param string $path Path to shorten
  291. * @return string Normalized path
  292. * @access public
  293. * @static
  294. */
  295. function trimPath($path) {
  296. if (!defined('CAKE_CORE_INCLUDE_PATH') || !defined('APP')) {
  297. return $path;
  298. }
  299. if (strpos($path, APP) === 0) {
  300. return str_replace(APP, 'APP' . DS, $path);
  301. } elseif (strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) {
  302. return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path);
  303. } elseif (strpos($path, ROOT) === 0) {
  304. return str_replace(ROOT, 'ROOT', $path);
  305. }
  306. $corePaths = Configure::corePaths('cake');
  307. foreach ($corePaths as $corePath) {
  308. if (strpos($path, $corePath) === 0) {
  309. return str_replace($corePath, 'CORE' .DS . 'cake' .DS, $path);
  310. }
  311. }
  312. return $path;
  313. }
  314. /**
  315. * Grabs an excerpt from a file and highlights a given line of code
  316. *
  317. * @param string $file Absolute path to a PHP file
  318. * @param integer $line Line number to highlight
  319. * @param integer $context Number of lines of context to extract above and below $line
  320. * @return array Set of lines highlighted
  321. * @access public
  322. * @static
  323. * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
  324. */
  325. function excerpt($file, $line, $context = 2) {
  326. $data = $lines = array();
  327. if (!file_exists($file)) {
  328. return array();
  329. }
  330. $data = @explode("\n", file_get_contents($file));
  331. if (empty($data) || !isset($data[$line])) {
  332. return;
  333. }
  334. for ($i = $line - ($context + 1); $i < $line + $context; $i++) {
  335. if (!isset($data[$i])) {
  336. continue;
  337. }
  338. $string = str_replace(array("\r\n", "\n"), "", highlight_string($data[$i], true));
  339. if ($i == $line) {
  340. $lines[] = '<span class="code-highlight">' . $string . '</span>';
  341. } else {
  342. $lines[] = $string;
  343. }
  344. }
  345. return $lines;
  346. }
  347. /**
  348. * Converts a variable to a string for debug output.
  349. *
  350. * @param string $var Variable to convert
  351. * @return string Variable as a formatted string
  352. * @access public
  353. * @static
  354. * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
  355. */
  356. function exportVar($var, $recursion = 0) {
  357. $_this = Debugger::getInstance();
  358. switch (strtolower(gettype($var))) {
  359. case 'boolean':
  360. return ($var) ? 'true' : 'false';
  361. break;
  362. case 'integer':
  363. case 'double':
  364. return $var;
  365. break;
  366. case 'string':
  367. if (trim($var) == "") {
  368. return '""';
  369. }
  370. return '"' . h($var) . '"';
  371. break;
  372. case 'object':
  373. return get_class($var) . "\n" . $_this->__object($var);
  374. case 'array':
  375. $out = "array(";
  376. $vars = array();
  377. foreach ($var as $key => $val) {
  378. if ($recursion >= 0) {
  379. if (is_numeric($key)) {
  380. $vars[] = "\n\t" . $_this->exportVar($val, $recursion - 1);
  381. } else {
  382. $vars[] = "\n\t" .$_this->exportVar($key, $recursion - 1)
  383. . ' => ' . $_this->exportVar($val, $recursion - 1);
  384. }
  385. }
  386. }
  387. $n = null;
  388. if (!empty($vars)) {
  389. $n = "\n";
  390. }
  391. return $out . implode(",", $vars) . "{$n})";
  392. break;
  393. case 'resource':
  394. return strtolower(gettype($var));
  395. break;
  396. case 'null':
  397. return 'null';
  398. break;
  399. }
  400. }
  401. /**
  402. * Handles object to string conversion.
  403. *
  404. * @param string $var Object to convert
  405. * @return string
  406. * @access private
  407. * @see Debugger:exportVar()
  408. */
  409. function __object($var) {
  410. $out = array();
  411. if (is_object($var)) {
  412. $className = get_class($var);
  413. $objectVars = get_object_vars($var);
  414. foreach ($objectVars as $key => $value) {
  415. if (is_object($value)) {
  416. $value = get_class($value) . ' object';
  417. } elseif (is_array($value)) {
  418. $value = 'array';
  419. } elseif ($value === null) {
  420. $value = 'NULL';
  421. } elseif (in_array(gettype($value), array('boolean', 'integer', 'double', 'string', 'array', 'resource'))) {
  422. $value = Debugger::exportVar($value);
  423. }
  424. $out[] = "$className::$$key = " . $value;
  425. }
  426. }
  427. return implode("\n", $out);
  428. }
  429. /**
  430. * Handles object conversion to debug string.
  431. *
  432. * @param string $var Object to convert
  433. * @access protected
  434. */
  435. function output($format = 'js') {
  436. $_this = Debugger::getInstance();
  437. $data = null;
  438. if ($format === true && !empty($_this->__data)) {
  439. $data = $_this->__data;
  440. $_this->__data = array();
  441. $format = false;
  442. }
  443. $_this->_outputFormat = $format;
  444. return $data;
  445. }
  446. /**
  447. * Handles object conversion to debug string.
  448. *
  449. * @param string $var Object to convert
  450. * @access private
  451. */
  452. function _output($level, $error, $code, $helpCode, $description, $file, $line, $kontext) {
  453. $files = $this->trace(array('start' => 2, 'format' => 'points'));
  454. $listing = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1);
  455. $trace = $this->trace(array('start' => 2, 'depth' => '20'));
  456. $context = array();
  457. foreach ((array)$kontext as $var => $value) {
  458. $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1);
  459. }
  460. switch ($this->_outputFormat) {
  461. default:
  462. case 'js':
  463. $link = "document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
  464. $out = "<a href='javascript:void(0);' onclick='{$link}'><b>{$error}</b> ({$code})</a>: {$description} [<b>{$file}</b>, line <b>{$line}</b>]";
  465. if (Configure::read() > 0) {
  466. debug($out, false, false);
  467. echo '<div id="CakeStackTrace' . count($this->errors) . '" class="cake-stack-trace" style="display: none;">';
  468. $link = "document.getElementById(\"CakeErrorCode" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeErrorCode" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
  469. echo "<a href='javascript:void(0);' onclick='{$link}'>Code</a>";
  470. if (!empty($context)) {
  471. $link = "document.getElementById(\"CakeErrorContext" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeErrorContext" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
  472. echo " | <a href='javascript:void(0);' onclick='{$link}'>Context</a>";
  473. if (!empty($helpCode)) {
  474. echo " | <a href='{$this->helpPath}{$helpCode}' target='_blank'>Help</a>";
  475. }
  476. echo "<pre id=\"CakeErrorContext" . count($this->errors) . "\" class=\"cake-context\" style=\"display: none;\">";
  477. echo implode("\n", $context);
  478. echo "</pre>";
  479. }
  480. if (!empty($listing)) {
  481. echo "<div id=\"CakeErrorCode" . count($this->errors) . "\" class=\"cake-code-dump\" style=\"display: none;\">";
  482. pr(implode("\n", $listing) . "\n", false);
  483. echo '</div>';
  484. }
  485. pr($trace, false);
  486. echo '</div>';
  487. }
  488. break;
  489. case 'html':
  490. echo "<pre class=\"cake-debug\"><b>{$error}</b> ({$code}) : {$description} [<b>{$file}</b>, line <b>{$line}]</b></pre>";
  491. if (!empty($context)) {
  492. echo "Context:\n" .implode("\n", $context) . "\n";
  493. }
  494. echo "<pre class=\"cake-debug context\"><b>Context</b> <p>" . implode("\n", $context) . "</p></pre>";
  495. echo "<pre class=\"cake-debug trace\"><b>Trace</b> <p>" . $trace. "</p></pre>";
  496. break;
  497. case 'text':
  498. case 'txt':
  499. echo "{$error}: {$code} :: {$description} on line {$line} of {$file}\n";
  500. if (!empty($context)) {
  501. echo "Context:\n" .implode("\n", $context) . "\n";
  502. }
  503. echo "Trace:\n" . $trace;
  504. break;
  505. case 'log':
  506. $this->log(compact('error', 'code', 'description', 'line', 'file', 'context', 'trace'));
  507. break;
  508. case false:
  509. $this->__data[] = compact('error', 'code', 'description', 'line', 'file', 'context', 'trace');
  510. break;
  511. }
  512. }
  513. /**
  514. * Verifies that the application's salt value has been changed from the default value.
  515. *
  516. * @access public
  517. * @static
  518. */
  519. function checkSessionKey() {
  520. if (Configure::read('Security.salt') == 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') {
  521. trigger_error(__('Please change the value of \'Security.salt\' in app/config/core.php to a salt value specific to your application', true), E_USER_NOTICE);
  522. }
  523. }
  524. /**
  525. * Invokes the given debugger object as the current error handler, taking over control from the previous handler
  526. * in a stack-like hierarchy.
  527. *
  528. * @param object $debugger A reference to the Debugger object
  529. * @access public
  530. * @static
  531. * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
  532. */
  533. function invoke(&$debugger) {
  534. set_error_handler(array(&$debugger, 'handleError'));
  535. }
  536. }
  537. if (!defined('DISABLE_DEFAULT_ERROR_HANDLING')) {
  538. Debugger::invoke(Debugger::getInstance());
  539. }
  540. ?>