2007-02-20 16:57:43 +00:00
< ? php
/* SVN FILE: $Id$ */
/**
* Framework debugging and PHP error - handling class
*
* Provides enhanced logging , stack traces , and rendering debug views
*
* PHP versions 4 and 5
*
* CakePHP ( tm ) : Rapid Development Framework < http :// www . cakephp . org />
* Copyright 2005 - 2007 , Cake Software Foundation , Inc .
* 1785 E . Sahara Avenue , Suite 490 - 204
* Las Vegas , Nevada 89104
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice .
*
* @ filesource
* @ copyright Copyright 2005 - 2007 , Cake Software Foundation , Inc .
* @ link http :// www . cakefoundation . org / projects / info / cakephp CakePHP ( tm ) Project
* @ package cake
* @ subpackage cake . cake . libs
* @ since CakePHP ( tm ) v 1.2 . 4560
* @ version $Revision $
* @ modifiedby $LastChangedBy $
* @ lastmodified $Date $
* @ license http :// www . opensource . org / licenses / mit - license . php The MIT License
*/
/**
* Included libraries .
*
*/
if ( ! class_exists ( 'Object' )) {
uses ( 'object' );
}
2007-04-12 00:34:16 +00:00
uses ( 'cake_log' );
2007-02-20 16:57:43 +00:00
/**
* Provide custom logging and error handling .
*
* Debugger overrides PHP ' s default error handling to provide stack traces and enhanced logging
*
* @ package cake
* @ subpackage cake . cake . libs
*/
class Debugger extends Object {
/**
* Holds a reference to errors generated by the application
*
* @ var array
* @ access public
*/
var $errors = array ();
/**
* Contains the base URL for error code documentation
*
* @ var string
* @ access public
*/
var $helpPath = null ;
/**
* Constructor
*
*/
function __construct () {
$docRef = ini_get ( 'docref_root' );
if ( empty ( $docRef )) {
ini_set ( 'docref_root' , 'http://php.net/' );
}
if ( ! defined ( 'E_RECOVERABLE_ERROR' )) {
define ( 'E_RECOVERABLE_ERROR' , 4096 );
}
}
/**
* Gets a reference to the Debugger object instance
*
* @ return object
2007-05-20 06:30:19 +00:00
* @ access public
2007-02-20 16:57:43 +00:00
*/
function & getInstance () {
static $instance = array ();
if ( ! isset ( $instance [ 0 ]) || ! $instance [ 0 ]) {
2007-08-11 15:03:28 +00:00
$instance [ 0 ] =& new Debugger ();
if ( Configure :: read () > 0 ) {
Configure :: version (); // Make sure the core config is loaded
$instance [ 0 ] -> helpPath = Configure :: read ( 'Cake.Debugger.HelpPath' );
}
2007-02-20 16:57:43 +00:00
}
return $instance [ 0 ];
}
/**
* Overrides PHP ' s default error handling
*
2007-05-20 06:30:19 +00:00
* @ param int $code Code of error
* @ param string $description Error description
* @ param string $file File on which error occurred
* @ param int $line Line that triggered the error
* @ param array $context Context
* @ return boolean true if error was handled
* @ access public
2007-02-20 16:57:43 +00:00
*/
function handleError ( $code , $description , $file = null , $line = null , $context = null ) {
2007-08-13 15:59:16 +00:00
if ( error_reporting () == 0 || $code === 2048 ) {
2007-02-20 16:57:43 +00:00
return ;
}
2007-08-13 15:59:16 +00:00
$_this = Debugger :: getInstance ();
2007-02-20 16:57:43 +00:00
if ( empty ( $file )) {
$file = '[internal]' ;
}
if ( empty ( $line )) {
$line = '??' ;
}
2007-08-11 15:03:28 +00:00
$file = $_this -> trimPath ( $file );
2007-02-20 16:57:43 +00:00
$info = compact ( 'code' , 'description' , 'file' , 'line' );
2007-08-11 15:03:28 +00:00
if ( ! in_array ( $info , $_this -> errors )) {
$_this -> errors [] = $info ;
2007-02-20 16:57:43 +00:00
} else {
return ;
}
2007-04-12 00:34:16 +00:00
$level = LOG_DEBUG ;
2007-02-20 16:57:43 +00:00
switch ( $code ) {
case E_PARSE :
case E_ERROR :
case E_CORE_ERROR :
case E_COMPILE_ERROR :
$error = 'Fatal Error' ;
2007-04-12 00:34:16 +00:00
$level = LOG_ERROR ;
2007-02-20 16:57:43 +00:00
break ;
case E_WARNING :
case E_USER_WARNING :
case E_COMPILE_WARNING :
case E_RECOVERABLE_ERROR :
$error = 'Warning' ;
2007-04-12 00:34:16 +00:00
$level = LOG_WARNING ;
2007-02-20 16:57:43 +00:00
break ;
case E_NOTICE :
$error = 'Notice' ;
2007-04-12 00:34:16 +00:00
$level = LOG_NOTICE ;
2007-02-20 16:57:43 +00:00
break ;
default :
return false ;
break ;
}
$helpCode = null ;
2007-08-11 15:03:28 +00:00
if ( ! empty ( $_this -> helpPath ) && preg_match ( '/.*\[([0-9]+)\]$/' , $description , $codes )) {
2007-02-20 16:57:43 +00:00
if ( isset ( $codes [ 1 ])) {
$helpCode = $codes [ 1 ];
$description = trim ( preg_replace ( '/\[[0-9]+\]$/' , '' , $description ));
}
}
2007-08-11 15:03:28 +00:00
$link = " document.getElementById( \" CakeStackTrace " . count ( $_this -> errors ) . " \" ).style.display = (document.getElementById( \" CakeStackTrace " . count ( $_this -> errors ) . " \" ).style.display == \" none \" ? \" \" : \" none \" ) " ;
2007-02-20 16:57:43 +00:00
$out = " <a href='javascript:void(0);' onclick=' { $link } '><b> { $error } </b> ( { $code } )</a>: { $description } [<b> { $file } </b>, line <b> { $line } </b>] " ;
2007-04-18 16:39:11 +00:00
if ( Configure :: read () > 0 ) {
debug ( $out );
2007-08-11 15:03:28 +00:00
e ( '<div id="CakeStackTrace' . count ( $_this -> errors ) . '" class="cake-stack-trace" style="display: none;">' );
2007-04-18 16:39:11 +00:00
if ( ! empty ( $context )) {
2007-08-11 15:03:28 +00:00
$link = " document.getElementById( \" CakeErrorContext " . count ( $_this -> errors ) . " \" ).style.display = (document.getElementById( \" CakeErrorContext " . count ( $_this -> errors ) . " \" ).style.display == \" none \" ? \" \" : \" none \" ) " ;
2007-04-18 16:39:11 +00:00
e ( " <a href='javascript:void(0);' onclick=' { $link } '>Context</a> | " );
2007-08-11 15:03:28 +00:00
$link = " document.getElementById( \" CakeErrorCode " . count ( $_this -> errors ) . " \" ).style.display = (document.getElementById( \" CakeErrorCode " . count ( $_this -> errors ) . " \" ).style.display == \" none \" ? \" \" : \" none \" ) " ;
2007-04-18 16:39:11 +00:00
e ( " <a href='javascript:void(0);' onclick=' { $link } '>Code</a> " );
if ( ! empty ( $helpCode )) {
2007-08-11 15:03:28 +00:00
e ( " | <a href=' { $_this -> helpPath } { $helpCode } ' target='blank'>Help</a> " );
2007-04-18 16:39:11 +00:00
}
2007-08-11 15:03:28 +00:00
e ( " <pre id= \" CakeErrorContext " . count ( $_this -> errors ) . " \" class= \" cake-context \" style= \" display: none; \" > " );
2007-04-18 16:39:11 +00:00
foreach ( $context as $var => $value ) {
2007-08-11 15:03:28 +00:00
e ( " \$ { $var } \t = \t " . $_this -> exportVar ( $value , 1 ) . " \n " );
2007-04-18 16:39:11 +00:00
}
e ( " </pre> " );
2007-02-20 16:57:43 +00:00
}
}
2007-08-11 15:03:28 +00:00
$files = $_this -> trace ( array ( 'start' => 1 , 'format' => 'points' ));
2007-02-20 16:57:43 +00:00
$listing = Debugger :: excerpt ( $files [ 0 ][ 'file' ], $files [ 0 ][ 'line' ] - 1 , 2 );
2007-04-18 16:39:11 +00:00
if ( Configure :: read () > 0 ) {
2007-08-11 15:03:28 +00:00
e ( " <div id= \" CakeErrorCode " . count ( $_this -> errors ) . " \" class= \" cake-code-dump \" style= \" display: none; \" > " );
2007-04-18 16:39:11 +00:00
pr ( implode ( " \n " , $listing ));
e ( '</div>' );
2007-02-20 16:57:43 +00:00
2007-08-11 15:03:28 +00:00
pr ( $_this -> trace ( array ( 'start' => 1 )));
2007-04-18 16:39:11 +00:00
e ( '</div>' );
}
2007-04-12 00:34:16 +00:00
if ( Configure :: read ( 'log' )) {
CakeLog :: write ( $level , " { $error } ( { $code } ): { $description } in [ { $file } , line { $line } ] " );
}
2007-04-24 00:26:10 +00:00
2007-02-20 16:57:43 +00:00
if ( $error == 'Fatal Error' ) {
die ();
2007-04-24 00:26:10 +00:00
}
2007-02-20 16:57:43 +00:00
return true ;
}
/**
* Outputs a stack trace with the given options
*
2007-05-20 06:30:19 +00:00
* @ param array $options Format for outputting stack trace
* @ return string Formatted stack trace
* @ access protected
2007-02-20 16:57:43 +00:00
*/
function trace ( $options = array ()) {
$options = am ( array (
'depth' => 999 ,
'format' => '' ,
'args' => false ,
'start' => 0 ,
'scope' => null ,
'exclude' => null
),
$options
);
$backtrace = debug_backtrace ();
$back = array ();
for ( $i = $options [ 'start' ]; $i < count ( $backtrace ) && $i < $options [ 'depth' ]; $i ++ ) {
$trace = am (
array (
'file' => '[internal]' ,
'line' => '??'
),
$backtrace [ $i ]
);
if ( isset ( $backtrace [ $i + 1 ])) {
$next = am (
array (
'line' => '??' ,
'file' => '[internal]' ,
'class' => null ,
'function' => '[main]'
),
$backtrace [ $i + 1 ]
);
$function = $next [ 'function' ];
if ( ! empty ( $next [ 'class' ])) {
$function = $next [ 'class' ] . '::' . $function . '(' ;
if ( $options [ 'args' ] && isset ( $next [ 'args' ])) {
$args = array ();
foreach ( $next [ 'args' ] as $arg ) {
$args [] = Debugger :: exportVar ( $arg );
}
$function .= join ( ', ' , $args );
}
$function .= ')' ;
}
} else {
$function = '[main]' ;
}
if ( in_array ( $function , array ( 'call_user_func_array' , 'trigger_error' ))) {
continue ;
}
if ( $options [ 'format' ] == 'points' && $trace [ 'file' ] != '[internal]' ) {
$back [] = array ( 'file' => $trace [ 'file' ], 'line' => $trace [ 'line' ]);
} elseif ( empty ( $options [ 'format' ])) {
$back [] = $function . ' - ' . Debugger :: trimPath ( $trace [ 'file' ]) . ', line ' . $trace [ 'line' ];
}
}
if ( $options [ 'format' ] == 'array' || $options [ 'format' ] == 'points' ) {
return $back ;
}
return join ( " \n " , $back );
}
/**
* Shortens file paths by replacing the application base path with 'APP' , and the CakePHP core
* path with 'CORE'
*
2007-05-20 06:30:19 +00:00
* @ param string $path Path to shorten
* @ return string Normalized path
* @ access protected
2007-02-20 16:57:43 +00:00
*/
function trimPath ( $path ) {
if ( ! defined ( 'CAKE_CORE_INCLUDE_PATH' ) || ! defined ( 'APP' )) {
return $path ;
}
if ( strpos ( $path , CAKE_CORE_INCLUDE_PATH ) === 0 ) {
$path = r ( CAKE_CORE_INCLUDE_PATH , 'CORE' , $path );
} elseif ( strpos ( $path , APP ) === 0 ) {
$path = r ( APP , 'APP' . DS , $path );
} elseif ( strpos ( $path , ROOT ) === 0 ) {
$path = r ( ROOT , 'ROOT' . DS , $path );
}
return $path ;
}
/**
* Grabs an excerpt from a file and highlights a given line of code
*
* @ param string $file Absolute path to a PHP file
* @ param int $line Line number to highlight
* @ param int $context Number of lines of context to extract above and below $line
2007-05-20 06:30:19 +00:00
* @ return array Set of lines highlighted
* @ access protected
2007-02-20 16:57:43 +00:00
*/
function excerpt ( $file , $line , $context = 2 ) {
$data = $lines = array ();
$data = @ explode ( " \n " , file_get_contents ( $file ));
if ( empty ( $data ) || ! isset ( $data [ $line ])) {
return ;
}
for ( $i = $line - ( $context + 1 ); $i < $line + $context ; $i ++ ) {
if ( ! isset ( $data [ $i ])) {
continue ;
}
if ( $i == $line ) {
$lines [] = '<span class="code-highlight">' . highlight_string ( $data [ $i ], true ) . '</span>' ;
} else {
$lines [] = highlight_string ( $data [ $i ], true );
}
}
return $lines ;
}
/**
* Converts a variable to a string for debug output
*
2007-05-20 06:30:19 +00:00
* @ param string $var Variable to convert
* @ return string Variable as a formatted string
* @ access protected
2007-02-20 16:57:43 +00:00
*/
function exportVar ( $var , $recursion = 0 ) {
switch ( low ( gettype ( $var ))) {
case 'boolean' :
return ife ( $var , 'true' , 'false' );
break ;
case 'integer' :
case 'double' :
return $var ;
break ;
case 'string' :
return '"' . $var . '"' ;
break ;
2007-08-11 15:03:28 +00:00
case 'object' :
$var = get_object_vars ( $var );
2007-02-20 16:57:43 +00:00
case 'array' :
$out = 'array(' ;
2007-08-11 15:03:28 +00:00
if ( $recursion !== 0 ) {
2007-02-20 16:57:43 +00:00
$vars = array ();
foreach ( $var as $key => $val ) {
$vars [] = Debugger :: exportVar ( $key ) . ' => ' . Debugger :: exportVar ( $val , $recursion - 1 );
}
return $out . join ( ', ' , $vars ) . ')' ;
} else {
return 'array' ;
}
break ;
case 'resource' :
return low ( gettype ( $var ));
break ;
case 'null' :
return 'null' ;
break ;
}
}
2007-08-11 15:03:28 +00:00
function __object ( $var ) {
static $history = array ();
$serialized = serialize ( $var );
array_push ( $history , $serialized );
echo " \n " ;
if ( is_object ( $var )) {
$className = get_class ( $var );
$objectVars = get_object_vars ( $var );
foreach ( $objectVars as $key => $value ) {
$value = ife (( ! is_object ( $value ) && ! is_array ( $value ) && trim ( $value ) == " " ), " [empty string] " , $value );
if ( strpos ( $key , '_' , 0 ) !== 0 ) {
echo " $className :: $key = " ;
}
if ( is_object ( $value ) || is_array ( $value )) {
$serialized = serialize ( $value );
if ( in_array ( $serialized , $history , true )) {
$value = ife ( is_object ( $value ), " *RECURSION* -> " . get_class ( $value ), " *RECURSION* " );
}
}
if ( in_array ( gettype ( $value ), array ( 'boolean' , 'integer' , 'double' , 'string' , 'array' , 'resource' , 'object' , 'null' ))) {
if ( is_array ( $value )) {
foreach ( $value as $name => $output ) {
if ( is_numeric ( $name )) {
echo " $output " ;
} else {
echo " $name => $output " ;
}
}
echo " \n " ;
} else {
echo Debugger :: exportVar ( $value ) . " \n " ;
}
} else {
echo $value . " \n " ;
}
}
$objectMethods = get_class_methods ( $className );
foreach ( $objectMethods as $key => $value ) {
if ( strpos ( $value , '_' , 0 ) !== 0 ) {
echo " $className :: $value () \n " ;
}
}
}
array_pop ( $history );
}
2007-04-12 00:34:16 +00:00
/**
* Verify that the application ' s salt has been changed from the default value
*
2007-05-20 06:30:19 +00:00
* @ access public
2007-04-12 00:34:16 +00:00
*/
function checkSessionKey () {
if ( CAKE_SESSION_STRING == 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi' ) {
2007-05-20 06:30:19 +00:00
trigger_error ( __ ( 'Please change the value of CAKE_SESSION_STRING in app/config/core.php to a salt value specific to your application' , true ), E_USER_NOTICE );
2007-04-12 00:34:16 +00:00
}
}
/**
* Invokes the given debugger object as the current error handler , taking over control from the previous handler
* in a stack - like hierarchy .
*
* @ param object $debugger A reference to the Debugger object
2007-05-20 06:30:19 +00:00
* @ access public
2007-04-12 00:34:16 +00:00
*/
function invoke ( & $debugger ) {
set_error_handler ( array ( & $debugger , 'handleError' ));
}
2007-02-20 16:57:43 +00:00
}
if ( ! defined ( 'DISABLE_DEFAULT_ERROR_HANDLING' )) {
2007-04-12 00:34:16 +00:00
Debugger :: invoke ( Debugger :: getInstance ());
2007-02-20 16:57:43 +00:00
}
?>