diff options
Diffstat (limited to 'system/core/Kohana_Exception.php')
| -rw-r--r-- | system/core/Kohana_Exception.php | 621 | 
1 files changed, 621 insertions, 0 deletions
| diff --git a/system/core/Kohana_Exception.php b/system/core/Kohana_Exception.php new file mode 100644 index 00000000..0cbc472c --- /dev/null +++ b/system/core/Kohana_Exception.php @@ -0,0 +1,621 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Kohana Exceptions + * + * $Id: Kohana_Exception.php 4726 2009-12-23 18:58:53Z isaiah $ + * + * @package    Core + * @author     Kohana Team + * @copyright  (c) 2007-2009 Kohana Team + * @license    http://kohanaphp.com/license + */ + +class Kohana_Exception_Core extends Exception { + +	public static $enabled = FALSE; + +	// Template file +	public static $template = 'kohana/error'; + +	// Show stack traces in errors +	public static $trace_output = TRUE; + +	// Show source code in errors +	public static $source_output = TRUE; + +	// To hold unique identifier to distinguish error output +	protected $instance_identifier; + +	// Error code +	protected $code = E_KOHANA; + +	/** +	 * Creates a new translated exception. +	 * +	 * @param string error message +	 * @param array translation variables +	 * @return void +	 */ +	public function __construct($message, array $variables = NULL, $code = 0) +	{ +		$this->instance_identifier = uniqid(); + +		// Translate the error message +		$message = __($message, $variables); + +		// Sets $this->message the proper way +		parent::__construct($message, $code); +	} + +	/** +	 * Enable Kohana exception handling. +	 * +	 * @uses    Kohana_Exception::$template +	 * @return  void +	 */ +	public static function enable() +	{ +		if ( ! Kohana_Exception::$enabled) +		{ +			set_exception_handler(array('Kohana_Exception', 'handle')); + +			Kohana_Exception::$enabled = TRUE; +		} +	} + +	/** +	 * Disable Kohana exception handling. +	 * +	 * @return  void +	 */ +	public static function disable() +	{ +		if (Kohana_Exception::$enabled) +		{ +			restore_exception_handler(); + +			Kohana_Exception::$enabled = FALSE; +		} +	} + +	/** +	 * Get a single line of text representing the exception: +	 * +	 * Error [ Code ]: Message ~ File [ Line ] +	 * +	 * @param   object  Exception +	 * @return  string +	 */ +	public static function text($e) +	{ +		return sprintf('%s [ %s ]: %s ~ %s [ %d ]', +			get_class($e), $e->getCode(), strip_tags($e->getMessage()), Kohana_Exception::debug_path($e->getFile()), $e->getLine()); +	} + +	/** +	 * exception handler, displays the error message, source of the +	 * exception, and the stack trace of the error. +	 * +	 * @uses    Kohana::message() +	 * @uses    Kohana_Exception::text() +	 * @param   object   exception object +	 * @return  void +	 */ +	public static function handle(Exception $e) +	{ +		try +		{ +			// Get the exception information +			$type    = get_class($e); +			$code    = $e->getCode(); +			$message = $e->getMessage(); + +			// Create a text version of the exception +			$error = Kohana_Exception::text($e); + +			// Add this exception to the log +			Kohana_Log::add('error', $error); + +			// Manually save logs after exceptions +			Kohana_Log::save(); + +			if (Kohana::config('kohana/core.display_errors') === FALSE) +			{ +				// Do not show the details +				$file = $line = NULL; +				$trace = array(); + +				$template = '_disabled'; +			} +			else +			{ +				$file = $e->getFile(); +				$line = $e->getLine(); +				$trace = $e->getTrace(); + +				$template = ''; +			} + +			if ($e instanceof Kohana_Exception) +			{ +				$template = $e->getTemplate().$template; + +				if ( ! headers_sent()) +				{ +					$e->sendHeaders(); +				} + +				// Use the human-readable error name +				$code = Kohana::message('kohana/core.errors.'.$code); +			} +			else +			{ +				$template = Kohana_Exception::$template.$template; + +				if ( ! headers_sent()) +				{ +					header('HTTP/1.1 500 Internal Server Error'); +				} + +				if ($e instanceof ErrorException) +				{ +					// Use the human-readable error name +					$code = Kohana::message('kohana/core.errors.'.$e->getSeverity()); + +					if (version_compare(PHP_VERSION, '5.3', '<')) +					{ +						// Workaround for a bug in ErrorException::getTrace() that exists in +						// all PHP 5.2 versions. @see http://bugs.php.net/45895 +						for ($i = count($trace) - 1; $i > 0; --$i) +						{ +							if (isset($trace[$i - 1]['args'])) +							{ +								// Re-position the arguments +								$trace[$i]['args'] = $trace[$i - 1]['args']; + +								unset($trace[$i - 1]['args']); +							} +						} +					} +				} +			} + +			// Clean the output buffer if one exists +			ob_get_level() and ob_clean(); + +			if ($template = Kohana::find_file('views', $template)) +			{ +				include $template; +			} +		} +		catch (Exception $e) +		{ +			// Clean the output buffer if one exists +			ob_get_level() and ob_clean(); + +			// Display the exception text +			echo Kohana_Exception::text($e), "\n"; +		} + +		if (Kohana::$server_api === 'cli') +		{ +			// Exit with an error status +			exit(1); +		} +	} + +	/** +	 * Returns the template for this exception. +	 * +	 * @uses    Kohana_Exception::$template +	 * @return  string +	 */ +	public function getTemplate() +	{ +		return Kohana_Exception::$template; +	} + +	/** +	 * Sends an Internal Server Error header. +	 * +	 * @return  void +	 */ +	public function sendHeaders() +	{ +		// Send the 500 header +		header('HTTP/1.1 500 Internal Server Error'); +	} + +	/** +	 * Returns an HTML string of information about a single variable. +	 * +	 * Borrows heavily on concepts from the Debug class of {@link http://nettephp.com/ Nette}. +	 * +	 * @param   mixed    variable to dump +	 * @param   integer  maximum length of strings +	 * @param   integer  maximum levels of recursion +	 * @return  string +	 */ +	public static function dump($value, $length = 128, $max_level = 5) +	{ +		return Kohana_Exception::_dump($value, $length, $max_level); +	} + +	/** +	 * Helper for Kohana_Exception::dump(), handles recursion in arrays and objects. +	 * +	 * @param   mixed    variable to dump +	 * @param   integer  maximum length of strings +	 * @param   integer  maximum levels of recursion +	 * @param   integer  current recursion level (internal) +	 * @return  string +	 */ +	private static function _dump( & $var, $length = 128, $max_level = 5, $level = 0) +	{ +		if ($var === NULL) +		{ +			return '<small>NULL</small>'; +		} +		elseif (is_bool($var)) +		{ +			return '<small>bool</small> '.($var ? 'TRUE' : 'FALSE'); +		} +		elseif (is_float($var)) +		{ +			return '<small>float</small> '.$var; +		} +		elseif (is_resource($var)) +		{ +			if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var)) +			{ +				$meta = stream_get_meta_data($var); + +				if (isset($meta['uri'])) +				{ +					$file = $meta['uri']; + +					if (function_exists('stream_is_local')) +					{ +						// Only exists on PHP >= 5.2.4 +						if (stream_is_local($file)) +						{ +							$file = Kohana_Exception::debug_path($file); +						} +					} + +					return '<small>resource</small><span>('.$type.')</span> '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::CHARSET); +				} +			} +			else +			{ +				return '<small>resource</small><span>('.$type.')</span>'; +			} +		} +		elseif (is_string($var)) +		{ +			if (strlen($var) > $length) +			{ +				// Encode the truncated string +				$str = htmlspecialchars(substr($var, 0, $length), ENT_NOQUOTES, Kohana::CHARSET).' …'; +			} +			else +			{ +				// Encode the string +				$str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::CHARSET); +			} + +			return '<small>string</small><span>('.strlen($var).')</span> "'.$str.'"'; +		} +		elseif (is_array($var)) +		{ +			$output = array(); + +			// Indentation for this variable +			$space = str_repeat($s = '    ', $level); + +			static $marker; + +			if ($marker === NULL) +			{ +				// Make a unique marker +				$marker = uniqid("\x00"); +			} + +			if (empty($var)) +			{ +				// Do nothing +			} +			elseif (isset($var[$marker])) +			{ +				$output[] = "(\n$space$s*RECURSION*\n$space)"; +			} +			elseif ($level <= $max_level) +			{ +				$output[] = "<span>("; + +				$var[$marker] = TRUE; +				foreach ($var as $key => & $val) +				{ +					if ($key === $marker) continue; +					if ( ! is_int($key)) +					{ +						$key = '"'.$key.'"'; +					} + +					$output[] = "$space$s$key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1); +				} +				unset($var[$marker]); + +				$output[] = "$space)</span>"; +			} +			else +			{ +				// Depth too great +				$output[] = "(\n$space$s...\n$space)"; +			} + +			return '<small>array</small><span>('.count($var).')</span> '.implode("\n", $output); +		} +		elseif (is_object($var)) +		{ +			// Copy the object as an array +			$array = (array) $var; + +			$output = array(); + +			// Indentation for this variable +			$space = str_repeat($s = '    ', $level); + +			$hash = spl_object_hash($var); + +			// Objects that are being dumped +			static $objects = array(); + +			if (empty($var)) +			{ +				// Do nothing +			} +			elseif (isset($objects[$hash])) +			{ +				$output[] = "{\n$space$s*RECURSION*\n$space}"; +			} +			elseif ($level <= $max_level) +			{ +				$output[] = "<code>{"; + +				$objects[$hash] = TRUE; +				foreach ($array as $key => & $val) +				{ +					if ($key[0] === "\x00") +					{ +						// Determine if the access is private or protected +						$access = '<small>'.($key[1] === '*' ? 'protected' : 'private').'</small>'; + +						// Remove the access level from the variable name +						$key = substr($key, strrpos($key, "\x00") + 1); +					} +					else +					{ +						$access = '<small>public</small>'; +					} + +					$output[] = "$space$s$access $key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1); +				} +				unset($objects[$hash]); + +				$output[] = "$space}</code>"; +			} +			else +			{ +				// Depth too great +				$output[] = "{\n$space$s...\n$space}"; +			} + +			return '<small>object</small> <span>'.get_class($var).'('.count($array).')</span> '.implode("\n", $output); +		} +		else +		{ +			return '<small>'.gettype($var).'</small> '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::CHARSET); +		} +	} + +	/** +	 * Removes APPPATH, SYSPATH, MODPATH, and DOCROOT from filenames, replacing +	 * them with the plain text equivalents. +	 * +	 * @param   string  path to sanitize +	 * @return  string +	 */ +	public static function debug_path($file) +	{ +		if (strpos($file, APPPATH) === 0) +		{ +			$file = 'APPPATH/'.substr($file, strlen(APPPATH)); +		} +		elseif (strpos($file, SYSPATH) === 0) +		{ +			$file = 'SYSPATH/'.substr($file, strlen(SYSPATH)); +		} +		elseif (strpos($file, MODPATH) === 0) +		{ +			$file = 'MODPATH/'.substr($file, strlen(MODPATH)); +		} +		elseif (strpos($file, DOCROOT) === 0) +		{ +			$file = 'DOCROOT/'.substr($file, strlen(DOCROOT)); +		} + +		return $file; +	} + +	/** +	 * Returns an array of lines from a file. +	 * +	 *     // Returns the current line of the current file +	 *     echo Kohana_Exception::debug_source(__FILE__, __LINE__); +	 * +	 * @param   string   file to open +	 * @param   integer  line number to find +	 * @param   integer  number of padding lines +	 * @return  array +	 */ +	public static function debug_source($file, $line_number, $padding = 5) +	{ +		// Make sure we can read the source file +		if ( ! is_readable($file)) +			return array(); + +		// Open the file and set the line position +		$file = fopen($file, 'r'); +		$line = 0; + +		// Set the reading range +		$range = array('start' => $line_number - $padding, 'end' => $line_number + $padding); + +		// Set the zero-padding amount for line numbers +		$format = '% '.strlen($range['end']).'d'; + +		$source = array(); +		while (($row = fgets($file)) !== FALSE) +		{ +			// Increment the line number +			if (++$line > $range['end']) +				break; + +			if ($line >= $range['start']) +			{ +				$source[sprintf($format, $line)] = $row; +			} +		} + +		// Close the file +		fclose($file); + +		return $source; +	} + +	/** +	 * Returns an array of strings that represent each step in the backtrace. +	 * +	 * @param   array  trace to analyze +	 * @return  array +	 */ +	public static function trace($trace = NULL) +	{ +		if ($trace === NULL) +		{ +			// Start a new trace +			$trace = debug_backtrace(); +		} + +		// Non-standard function calls +		$statements = array('include', 'include_once', 'require', 'require_once'); + +		$output = array(); +		foreach ($trace as $step) +		{ +			if ( ! isset($step['function'])) +			{ +				// Invalid trace step +				continue; +			} + +			if (isset($step['file']) AND isset($step['line'])) +			{ +				// Include the source of this step +				$source = Kohana_Exception::debug_source($step['file'], $step['line']); +			} + +			if (isset($step['file'])) +			{ +				$file = $step['file']; + +				if (isset($step['line'])) +				{ +					$line = $step['line']; +				} +			} + +			// function() +			$function = $step['function']; + +			if (in_array($step['function'], $statements)) +			{ +				if (empty($step['args'])) +				{ +					// No arguments +					$args = array(); +				} +				else +				{ +					// Sanitize the file path +					$args = array($step['args'][0]); +				} +			} +			elseif (isset($step['args'])) +			{ +				if ($step['function'] === '{closure}') +				{ +					// Introspection on closures in a stack trace is impossible +					$params = NULL; +				} +				else +				{ +					if (isset($step['class'])) +					{ +						if (method_exists($step['class'], $step['function'])) +						{ +							$reflection = new ReflectionMethod($step['class'], $step['function']); +						} +						else +						{ +							$reflection = new ReflectionMethod($step['class'], '__call'); +						} +					} +					else +					{ +						$reflection = new ReflectionFunction($step['function']); +					} + +					// Get the function parameters +					$params = $reflection->getParameters(); +				} + +				$args = array(); + +				foreach ($step['args'] as $i => $arg) +				{ +					if (isset($params[$i])) +					{ +						// Assign the argument by the parameter name +						$args[$params[$i]->name] = $arg; +					} +					else +					{ +						// Assign the argument by number +						$args[$i] = $arg; +					} +				} +			} + +			if (isset($step['class'])) +			{ +				// Class->method() or Class::method() +				$function = $step['class'].$step['type'].$step['function']; +			} + +			$output[] = array( +				'function' => $function, +				'args'     => isset($args)   ? $args : NULL, +				'file'     => isset($file)   ? $file : NULL, +				'line'     => isset($line)   ? $line : NULL, +				'source'   => isset($source) ? $source : NULL, +			); + +			unset($function, $args, $file, $line, $source); +		} + +		return $output; +	} + +} // End Kohana Exception | 
