Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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