debugger.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: debugger.php 8127 2009-03-23 18:59:37Z davidpersson $ */
00003 /**
00004  * Framework debugging and PHP error-handling class
00005  *
00006  * Provides enhanced logging, stack traces, and rendering debug views
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00011  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00012  *
00013  * Licensed under The MIT License
00014  * Redistributions of files must retain the above copyright notice.
00015  *
00016  * @filesource
00017  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00018  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00019  * @package       cake
00020  * @subpackage    cake.cake.libs
00021  * @since         CakePHP(tm) v 1.2.4560
00022  * @version       $Revision: 8127 $
00023  * @modifiedby    $LastChangedBy: davidpersson $
00024  * @lastmodified  $Date: 2009-03-23 14:59:37 -0400 (Mon, 23 Mar 2009) $
00025  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00026  */
00027 /**
00028  * Included libraries.
00029  *
00030  */
00031     if (!class_exists('Object')) {
00032         uses('object');
00033     }
00034     if (!class_exists('CakeLog')) {
00035         uses('cake_log');
00036     }
00037 /**
00038  * Provide custom logging and error handling.
00039  *
00040  * Debugger overrides PHP's default error handling to provide stack traces and enhanced logging
00041  *
00042  * @package       cake
00043  * @subpackage    cake.cake.libs
00044  * @link          http://book.cakephp.org/view/460/Using-the-Debugger-Class
00045  */
00046 class Debugger extends Object {
00047 /**
00048  * A list of errors generated by the application.
00049  *
00050  * @var array
00051  * @access public
00052  */
00053     var $errors = array();
00054 /**
00055  * Contains the base URL for error code documentation.
00056  *
00057  * @var string
00058  * @access public
00059  */
00060     var $helpPath = null;
00061 /**
00062  * The current output format.
00063  *
00064  * @var string
00065  * @access protected
00066  */
00067     var $_outputFormat = 'js';
00068 /**
00069  * Holds current output data when outputFormat is false.
00070  *
00071  * @var string
00072  * @access private
00073  */
00074     var $__data = array();
00075 /**
00076  * Constructor.
00077  *
00078  */
00079     function __construct() {
00080         $docRef = ini_get('docref_root');
00081         if (empty($docRef)) {
00082             ini_set('docref_root', 'http://php.net/');
00083         }
00084         if (!defined('E_RECOVERABLE_ERROR')) {
00085             define('E_RECOVERABLE_ERROR', 4096);
00086         }
00087     }
00088 /**
00089  * Returns a reference to the Debugger singleton object instance.
00090  *
00091  * @return object
00092  * @access public
00093  * @static
00094  */
00095     function &getInstance($class = null) {
00096         static $instance = array();
00097         if (!empty($class)) {
00098             if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) {
00099                 $instance[0] = & new $class();
00100                 if (Configure::read() > 0) {
00101                     Configure::version(); // Make sure the core config is loaded
00102                     $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath');
00103                 }
00104             }
00105         }
00106 
00107         if (!$instance) {
00108             $instance[0] =& new Debugger();
00109             if (Configure::read() > 0) {
00110                 Configure::version(); // Make sure the core config is loaded
00111                 $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath');
00112             }
00113         }
00114         return $instance[0];
00115     }
00116 /**
00117  * Formats and outputs the contents of the supplied variable.
00118  *
00119  * @param $var mixed the variable to dump
00120  * @return void
00121  * @see exportVar
00122  * @access public
00123  * @static
00124  * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
00125 */
00126     function dump($var) {
00127         $_this = Debugger::getInstance();
00128         pr($_this->exportVar($var));
00129     }
00130 /**
00131  * Creates a detailed stack trace log at the time of invocation, much like dump()
00132  * but to debug.log.
00133  *
00134  * @param $var mixed Variable or content to log
00135  * @param $level int type of log to use. Defaults to LOG_DEBUG
00136  * @return void
00137  * @static
00138  * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
00139  */
00140     function log($var, $level = LOG_DEBUG) {
00141         $_this = Debugger::getInstance();
00142         $trace = $_this->trace(array('start' => 1, 'depth' => 2, 'format' => 'array'));
00143         $source = null;
00144 
00145         if (is_object($trace[0]['object']) && isset($trace[0]['object']->_reporter->_test_stack)) {
00146             $stack = $trace[0]['object']->_reporter->_test_stack;
00147             $source = sprintf('[%1$s, %3$s::%2$s()]' . "\n",
00148                                 array_shift($stack), array_pop($stack), array_pop($stack));
00149         }
00150 
00151         CakeLog::write($level, $source . $_this->exportVar($var));
00152     }
00153 
00154 /**
00155  * Overrides PHP's default error handling.
00156  *
00157  * @param integer $code Code of error
00158  * @param string $description Error description
00159  * @param string $file File on which error occurred
00160  * @param integer $line Line that triggered the error
00161  * @param array $context Context
00162  * @return boolean true if error was handled
00163  * @access public
00164  */
00165     function handleError($code, $description, $file = null, $line = null, $context = null) {
00166         if (error_reporting() == 0 || $code === 2048) {
00167             return;
00168         }
00169 
00170         $_this = Debugger::getInstance();
00171 
00172         if (empty($file)) {
00173             $file = '[internal]';
00174         }
00175         if (empty($line)) {
00176             $line = '??';
00177         }
00178         $file = $_this->trimPath($file);
00179 
00180         $info = compact('code', 'description', 'file', 'line');
00181         if (!in_array($info, $_this->errors)) {
00182             $_this->errors[] = $info;
00183         } else {
00184             return;
00185         }
00186 
00187         $level = LOG_DEBUG;
00188         switch ($code) {
00189             case E_PARSE:
00190             case E_ERROR:
00191             case E_CORE_ERROR:
00192             case E_COMPILE_ERROR:
00193             case E_USER_ERROR:
00194                 $error = 'Fatal Error';
00195                 $level = LOG_ERROR;
00196             break;
00197             case E_WARNING:
00198             case E_USER_WARNING:
00199             case E_COMPILE_WARNING:
00200             case E_RECOVERABLE_ERROR:
00201                 $error = 'Warning';
00202                 $level = LOG_WARNING;
00203             break;
00204             case E_NOTICE:
00205             case E_USER_NOTICE:
00206                 $error = 'Notice';
00207                 $level = LOG_NOTICE;
00208             break;
00209             default:
00210                 return false;
00211             break;
00212         }
00213 
00214         $helpCode = null;
00215         if (!empty($_this->helpPath) && preg_match('/.*\[([0-9]+)\]$/', $description, $codes)) {
00216             if (isset($codes[1])) {
00217                 $helpCode = $codes[1];
00218                 $description = trim(preg_replace('/\[[0-9]+\]$/', '', $description));
00219             }
00220         }
00221 
00222         echo $_this->_output($level, $error, $code, $helpCode, $description, $file, $line, $context);
00223 
00224         if (Configure::read('log')) {
00225             CakeLog::write($level, "{$error} ({$code}): {$description} in [{$file}, line {$line}]");
00226         }
00227 
00228         if ($error == 'Fatal Error') {
00229             die();
00230         }
00231         return true;
00232     }
00233 /**
00234  * Outputs a stack trace based on the supplied options.
00235  *
00236  * @param array $options Format for outputting stack trace
00237  * @return string Formatted stack trace
00238  * @access public
00239  * @static
00240  * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
00241  */
00242     function trace($options = array()) {
00243         $options = array_merge(array(
00244                 'depth'     => 999,
00245                 'format'    => '',
00246                 'args'      => false,
00247                 'start'     => 0,
00248                 'scope'     => null,
00249                 'exclude'   => null
00250             ),
00251             $options
00252         );
00253 
00254         $backtrace = debug_backtrace();
00255         $back = array();
00256         $count = count($backtrace);
00257 
00258         for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
00259             $trace = array_merge(
00260                 array(
00261                     'file' => '[internal]',
00262                     'line' => '??'
00263                 ),
00264                 $backtrace[$i]
00265             );
00266 
00267             if (isset($backtrace[$i + 1])) {
00268                 $next = array_merge(
00269                     array(
00270                         'line'      => '??',
00271                         'file'      => '[internal]',
00272                         'class'     => null,
00273                         'function'  => '[main]'
00274                     ),
00275                     $backtrace[$i + 1]
00276                 );
00277                 $function = $next['function'];
00278 
00279                 if (!empty($next['class'])) {
00280                     $function = $next['class'] . '::' . $function . '(';
00281                     if ($options['args'] && isset($next['args'])) {
00282                         $args = array();
00283                         foreach ($next['args'] as $arg) {
00284                             $args[] = Debugger::exportVar($arg);
00285                         }
00286                         $function .= join(', ', $args);
00287                     }
00288                     $function .= ')';
00289                 }
00290             } else {
00291                 $function = '[main]';
00292             }
00293             if (in_array($function, array('call_user_func_array', 'trigger_error'))) {
00294                 continue;
00295             }
00296             if ($options['format'] == 'points' && $trace['file'] != '[internal]') {
00297                 $back[] = array('file' => $trace['file'], 'line' => $trace['line']);
00298             } elseif (empty($options['format'])) {
00299                 $back[] = $function . ' - ' . Debugger::trimPath($trace['file']) . ', line ' . $trace['line'];
00300             } else {
00301                 $back[] = $trace;
00302             }
00303         }
00304 
00305         if ($options['format'] == 'array' || $options['format'] == 'points') {
00306             return $back;
00307         }
00308         return join("\n", $back);
00309     }
00310 /**
00311  * Shortens file paths by replacing the application base path with 'APP', and the CakePHP core
00312  * path with 'CORE'.
00313  *
00314  * @param string $path Path to shorten
00315  * @return string Normalized path
00316  * @access public
00317  * @static
00318  */
00319     function trimPath($path) {
00320         if (!defined('CAKE_CORE_INCLUDE_PATH') || !defined('APP')) {
00321             return $path;
00322         }
00323 
00324         if (strpos($path, APP) === 0) {
00325             return str_replace(APP, 'APP' . DS, $path);
00326         } elseif (strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) {
00327             return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path);
00328         } elseif (strpos($path, ROOT) === 0) {
00329             return str_replace(ROOT, 'ROOT', $path);
00330         }
00331         $corePaths = Configure::corePaths('cake');
00332         foreach ($corePaths as $corePath) {
00333             if (strpos($path, $corePath) === 0) {
00334                 return str_replace($corePath, 'CORE' .DS . 'cake' .DS, $path);
00335             }
00336         }
00337         return $path;
00338     }
00339 /**
00340  * Grabs an excerpt from a file and highlights a given line of code
00341  *
00342  * @param string $file Absolute path to a PHP file
00343  * @param integer $line Line number to highlight
00344  * @param integer $context Number of lines of context to extract above and below $line
00345  * @return array Set of lines highlighted
00346  * @access public
00347  * @static
00348  * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
00349  */
00350     function excerpt($file, $line, $context = 2) {
00351         $data = $lines = array();
00352         if (!file_exists($file)) {
00353             return array();
00354         }
00355         $data = @explode("\n", file_get_contents($file));
00356 
00357         if (empty($data) || !isset($data[$line])) {
00358             return;
00359         }
00360         for ($i = $line - ($context + 1); $i < $line + $context; $i++) {
00361             if (!isset($data[$i])) {
00362                 continue;
00363             }
00364             $string = str_replace(array("\r\n", "\n"), "", highlight_string($data[$i], true));
00365             if ($i == $line) {
00366                 $lines[] = '<span class="code-highlight">' . $string . '</span>';
00367             } else {
00368                 $lines[] = $string;
00369             }
00370         }
00371         return $lines;
00372     }
00373 /**
00374  * Converts a variable to a string for debug output.
00375  *
00376  * @param string $var Variable to convert
00377  * @return string Variable as a formatted string
00378  * @access public
00379  * @static
00380  * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
00381  */
00382     function exportVar($var, $recursion = 0) {
00383         $_this =  Debugger::getInstance();
00384         switch (strtolower(gettype($var))) {
00385             case 'boolean':
00386                 return ($var) ? 'true' : 'false';
00387             break;
00388             case 'integer':
00389             case 'double':
00390                 return $var;
00391             break;
00392             case 'string':
00393                 if (trim($var) == "") {
00394                     return '""';
00395                 }
00396                 return '"' . h($var) . '"';
00397             break;
00398             case 'object':
00399                 return get_class($var) . "\n" . $_this->__object($var);
00400             case 'array':
00401                 $out = "array(";
00402                 $vars = array();
00403                 foreach ($var as $key => $val) {
00404                     if ($recursion >= 0) {
00405                         if (is_numeric($key)) {
00406                             $vars[] = "\n\t" . $_this->exportVar($val, $recursion - 1);
00407                         } else {
00408                             $vars[] = "\n\t" .$_this->exportVar($key, $recursion - 1)
00409                                         . ' => ' . $_this->exportVar($val, $recursion - 1);
00410                         }
00411                     }
00412                 }
00413                 $n = null;
00414                 if (count($vars) > 0) {
00415                     $n = "\n";
00416                 }
00417                 return $out . join(",", $vars) . "{$n})";
00418             break;
00419             case 'resource':
00420                 return strtolower(gettype($var));
00421             break;
00422             case 'null':
00423                 return 'null';
00424             break;
00425         }
00426     }
00427 /**
00428  * Handles object to string conversion.
00429  *
00430  * @param string $var Object to convert
00431  * @return string
00432  * @access private
00433  * @see Debugger:exportVar()
00434  */
00435     function __object($var) {
00436         $out = array();
00437 
00438         if (is_object($var)) {
00439             $className = get_class($var);
00440             $objectVars = get_object_vars($var);
00441 
00442             foreach ($objectVars as $key => $value) {
00443                 if (is_object($value)) {
00444                     $value = get_class($value) . ' object';
00445                 } elseif (is_array($value)) {
00446                     $value = 'array';
00447                 } elseif ($value === null) {
00448                     $value = 'NULL';
00449                 } elseif (in_array(gettype($value), array('boolean', 'integer', 'double', 'string', 'array', 'resource'))) {
00450                     $value = Debugger::exportVar($value);
00451                 }
00452                 $out[] = "$className::$$key = " . $value;
00453             }
00454         }
00455         return join("\n", $out);
00456     }
00457 /**
00458  * Handles object conversion to debug string.
00459  *
00460  * @param string $var Object to convert
00461  * @access protected
00462  */
00463     function output($format = 'js') {
00464         $_this = Debugger::getInstance();
00465         $data = null;
00466 
00467         if ($format === true && !empty($_this->__data)) {
00468             $data = $_this->__data;
00469             $_this->__data = array();
00470             $format = false;
00471         }
00472         $_this->_outputFormat = $format;
00473 
00474         return $data;
00475     }
00476 /**
00477  * Handles object conversion to debug string.
00478  *
00479  * @param string $var Object to convert
00480  * @access private
00481  */
00482     function _output($level, $error, $code, $helpCode, $description, $file, $line, $kontext) {
00483         $files = $this->trace(array('start' => 2, 'format' => 'points'));
00484         $listing = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1);
00485         $trace = $this->trace(array('start' => 2, 'depth' => '20'));
00486         $context = array();
00487 
00488         foreach ((array)$kontext as $var => $value) {
00489             $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1);
00490         }
00491 
00492         switch ($this->_outputFormat) {
00493             default:
00494             case 'js':
00495                 $link = "document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
00496                 $out = "<a href='javascript:void(0);' onclick='{$link}'><b>{$error}</b> ({$code})</a>: {$description} [<b>{$file}</b>, line <b>{$line}</b>]";
00497                 if (Configure::read() > 0) {
00498                     debug($out, false, false);
00499                     echo '<div id="CakeStackTrace' . count($this->errors) . '" class="cake-stack-trace" style="display: none;">';
00500                         $link = "document.getElementById(\"CakeErrorCode" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeErrorCode" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
00501                         echo "<a href='javascript:void(0);' onclick='{$link}'>Code</a>";
00502 
00503                         if (!empty($context)) {
00504                             $link = "document.getElementById(\"CakeErrorContext" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeErrorContext" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
00505                             echo " | <a href='javascript:void(0);' onclick='{$link}'>Context</a>";
00506 
00507                             if (!empty($helpCode)) {
00508                                 echo " | <a href='{$this->helpPath}{$helpCode}' target='_blank'>Help</a>";
00509                             }
00510 
00511                             echo "<pre id=\"CakeErrorContext" . count($this->errors) . "\" class=\"cake-context\" style=\"display: none;\">";
00512                             echo implode("\n", $context);
00513                             echo "</pre>";
00514                         }
00515 
00516                         if (!empty($listing)) {
00517                             echo "<div id=\"CakeErrorCode" . count($this->errors) . "\" class=\"cake-code-dump\" style=\"display: none;\">";
00518                                 pr(implode("\n", $listing) . "\n", false);
00519                             echo '</div>';
00520                         }
00521                         pr($trace, false);
00522                     echo '</div>';
00523                 }
00524             break;
00525             case 'html':
00526                 echo "<pre class=\"cake-debug\"><b>{$error}</b> ({$code}) : {$description} [<b>{$file}</b>, line <b>{$line}]</b></pre>";
00527                 if (!empty($context)) {
00528                     echo "Context:\n" .implode("\n", $context) . "\n";
00529                 }
00530                 echo "<pre class=\"cake-debug context\"><b>Context</b> <p>" . implode("\n", $context) . "</p></pre>";
00531                 echo "<pre class=\"cake-debug trace\"><b>Trace</b> <p>" . $trace. "</p></pre>";
00532             break;
00533             case 'text':
00534             case 'txt':
00535                 echo "{$error}: {$code} :: {$description} on line {$line} of {$file}\n";
00536                 if (!empty($context)) {
00537                     echo "Context:\n" .implode("\n", $context) . "\n";
00538                 }
00539                 echo "Trace:\n" . $trace;
00540             break;
00541             case 'log':
00542                 $this->log(compact('error', 'code', 'description', 'line', 'file', 'context', 'trace'));
00543             break;
00544             case false:
00545                 $this->__data[] = compact('error', 'code', 'description', 'line', 'file', 'context', 'trace');
00546             break;
00547         }
00548     }
00549 /**
00550  * Verifies that the application's salt value has been changed from the default value.
00551  *
00552  * @access public
00553  * @static
00554  */
00555     function checkSessionKey() {
00556         if (Configure::read('Security.salt') == 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') {
00557             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);
00558         }
00559     }
00560 /**
00561  * Invokes the given debugger object as the current error handler, taking over control from the previous handler
00562  * in a stack-like hierarchy.
00563  *
00564  * @param object $debugger A reference to the Debugger object
00565  * @access public
00566  * @static
00567  * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
00568  */
00569     function invoke(&$debugger) {
00570         set_error_handler(array(&$debugger, 'handleError'));
00571     }
00572 }
00573 
00574 if (!defined('DISABLE_DEFAULT_ERROR_HANDLING')) {
00575     Debugger::invoke(Debugger::getInstance());
00576 }
00577 ?>

Generated on Sun Nov 22 00:30:53 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7