You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							507 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
	
	
							507 lines
						
					
					
						
							15 KiB
						
					
					
				<?php | 
						|
/** | 
						|
 * ErrorHandler class file. | 
						|
 * | 
						|
 * @link http://www.yiiframework.com/ | 
						|
 * @copyright Copyright © 2008-2012 Yii Software LLC | 
						|
 * @license http://www.yiiframework.com/license/ | 
						|
 */ | 
						|
 | 
						|
namespace yii\base; | 
						|
 | 
						|
/** | 
						|
 * ErrorHandler handles uncaught PHP errors and exceptions. | 
						|
 * | 
						|
 * It displays these errors using appropriate views based on the | 
						|
 * nature of the error and the mode the application runs at. | 
						|
 * It also chooses the most preferred language for displaying the error. | 
						|
 * | 
						|
 * ErrorHandler uses two sets of views: | 
						|
 * <ul> | 
						|
 * <li>development views, named as <code>exception.php</code>; | 
						|
 * <li>production views, named as <code>error<StatusCode>.php</code>; | 
						|
 * </ul> | 
						|
 * where <StatusCode> stands for the HTTP error code (e.g. error500.php). | 
						|
 * Localized views are named similarly but located under a subdirectory | 
						|
 * whose name is the language code (e.g. zh_cn/error500.php). | 
						|
 * | 
						|
 * Development views are displayed when the application is in debug mode | 
						|
 * (i.e. YII_DEBUG is defined as true). Detailed error information with source code | 
						|
 * are displayed in these views. Production views are meant to be shown | 
						|
 * to end-users and are used when the application is in production mode. | 
						|
 * For security reasons, they only display the error message without any | 
						|
 * sensitive information. | 
						|
 * | 
						|
 * ErrorHandler looks for the view templates from the following locations in order: | 
						|
 * <ol> | 
						|
 * <li><code>themes/ThemeName/views/system</code>: when a theme is active.</li> | 
						|
 * <li><code>protected/views/system</code></li> | 
						|
 * <li><code>framework/views</code></li> | 
						|
 * </ol> | 
						|
 * If the view is not found in a directory, it will be looked for in the next directory. | 
						|
 * | 
						|
 * The property {@link maxSourceLines} can be changed to specify the number | 
						|
 * of source code lines to be displayed in development views. | 
						|
 * | 
						|
 * ErrorHandler is a core application component that can be accessed via | 
						|
 * {@link CApplication::getErrorHandler()}. | 
						|
 * | 
						|
 * @property array $error The error details. Null if there is no error. | 
						|
 * | 
						|
 * @author Qiang Xue <qiang.xue@gmail.com> | 
						|
 * @since 2.0 | 
						|
 */ | 
						|
class ErrorHandler extends ApplicationComponent | 
						|
{ | 
						|
	/** | 
						|
	 * @var integer maximum number of source code lines to be displayed. Defaults to 25. | 
						|
	 */ | 
						|
	public $maxSourceLines = 25; | 
						|
 | 
						|
	/** | 
						|
	 * @var integer maximum number of trace source code lines to be displayed. Defaults to 10. | 
						|
	 */ | 
						|
	public $maxTraceSourceLines = 10; | 
						|
 | 
						|
	/** | 
						|
	 * @var string the application administrator information (could be a name or email link). It is displayed in error pages to end users. Defaults to 'the webmaster'. | 
						|
	 */ | 
						|
	public $adminInfo = 'the webmaster'; | 
						|
	/** | 
						|
	 * @var boolean whether to discard any existing page output before error display. Defaults to true. | 
						|
	 */ | 
						|
	public $discardExistingOutput = true; | 
						|
	/** | 
						|
	 * @var string the route (eg 'site/error') to the controller action that will be used to display external errors. | 
						|
	 * Inside the action, it can retrieve the error information by Yii::app()->errorHandler->error. | 
						|
	 * This property defaults to null, meaning ErrorHandler will handle the error display. | 
						|
	 */ | 
						|
	public $errorAction; | 
						|
 | 
						|
	private $_error; | 
						|
	public $exception; | 
						|
	public $compactOutput; | 
						|
 | 
						|
	public function init() | 
						|
	{ | 
						|
		set_exception_handler(array($this, 'handleException')); | 
						|
		set_error_handler(array($this, 'handleError'), error_reporting()); | 
						|
	} | 
						|
 | 
						|
	protected function logException($exception) | 
						|
	{ | 
						|
		$category = get_class($exception); | 
						|
		if ($exception instanceof HttpException) { | 
						|
			$category .= '\\' . $exception->statusCode; | 
						|
		} elseif ($exception instanceof \ErrorException) { | 
						|
			$category .= '\\' . $exception->getSeverity(); | 
						|
		} | 
						|
		\Yii::error((string)$exception, $category); | 
						|
	} | 
						|
 | 
						|
	protected function clearOutput() | 
						|
	{ | 
						|
		// the following manual level counting is to deal with zlib.output_compression set to On | 
						|
		for ($level = ob_get_level(); $level > 0; --$level) { | 
						|
			@ob_end_clean(); | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	protected function simple() | 
						|
	{ | 
						|
		if (YII_DEBUG) { | 
						|
			echo '<h1>' . get_class($exception) . "</h1>\n"; | 
						|
			echo '<p>' . $exception->getMessage() . ' (' . $exception->getFile() . ':' . $exception->getLine() . ')</p>'; | 
						|
			echo '<pre>' . $exception->getTraceAsString() . '</pre>'; | 
						|
		} else | 
						|
		{ | 
						|
			echo '<h1>' . get_class($exception) . "</h1>\n"; | 
						|
			echo '<p>' . $exception->getMessage() . '</p>'; | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * @param \Exception $exception | 
						|
	 */ | 
						|
	public function handleException($exception) | 
						|
	{ | 
						|
		$this->exception = $exception; | 
						|
 | 
						|
		// disable error capturing to avoid recursive errors | 
						|
		restore_error_handler(); | 
						|
		restore_exception_handler(); | 
						|
 | 
						|
		$this->logException($exception); | 
						|
		if ($this->discardExistingOutput) { | 
						|
			$this->clearOutput(); | 
						|
		} | 
						|
 | 
						|
		if ($this->compactOutput === null) { | 
						|
			// not in Web application, or not in AJAX request | 
						|
			$this->compactOutput = !(\Yii::$application instanceof \yii\web\Application) || isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; | 
						|
		} | 
						|
 | 
						|
		if ($this->compactOutput) { | 
						|
			$this->simple(); | 
						|
			return; | 
						|
		} | 
						|
 | 
						|
		if (($trace = $this->getExactTrace($exception)) === null) { | 
						|
			$fileName = $exception->getFile(); | 
						|
			$errorLine = $exception->getLine(); | 
						|
		} else { | 
						|
			$fileName = $trace['file']; | 
						|
			$errorLine = $trace['line']; | 
						|
		} | 
						|
 | 
						|
		$trace = $exception->getTrace(); | 
						|
		foreach ($trace as $i => $t) { | 
						|
			if (!isset($t['file'])) { | 
						|
				$trace[$i]['file'] = 'unknown'; | 
						|
			} | 
						|
			if (!isset($t['line'])) { | 
						|
				$trace[$i]['line'] = 0; | 
						|
			} | 
						|
			if (!isset($t['function'])) { | 
						|
				$trace[$i]['function'] = 'unknown'; | 
						|
			} | 
						|
			unset($trace[$i]['object']); | 
						|
		} | 
						|
 | 
						|
		$this->_error = $data = array( | 
						|
			'code' => $exception instanceof HttpException ? $exception->statusCode : 500, | 
						|
			'type' => get_class($exception), | 
						|
			'errorCode' => $exception->getCode(), | 
						|
			'message' => $exception->getMessage(), | 
						|
			'file' => $fileName, | 
						|
			'line' => $errorLine, | 
						|
			'trace' => $exception->getTraceAsString(), | 
						|
			'traces' => $trace, | 
						|
		); | 
						|
 | 
						|
		if (!headers_sent()) { | 
						|
			header("HTTP/1.0 {$data['code']} " . get_class($exception)); | 
						|
		} | 
						|
 | 
						|
		if ($exception instanceof HttpException || !YII_DEBUG) { | 
						|
			$this->render('error', $data); | 
						|
		} else { | 
						|
			$this->render('exception', $data); | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Returns the details about the error that is currently being handled. | 
						|
	 * The error is returned in terms of an array, with the following information: | 
						|
	 * <ul> | 
						|
	 * <li>code - the HTTP status code (e.g. 403, 500)</li> | 
						|
	 * <li>type - the error type (e.g. 'CHttpException', 'PHP Error')</li> | 
						|
	 * <li>message - the error message</li> | 
						|
	 * <li>file - the name of the PHP script file where the error occurs</li> | 
						|
	 * <li>line - the line number of the code where the error occurs</li> | 
						|
	 * <li>trace - the call stack of the error</li> | 
						|
	 * <li>source - the context source code where the error occurs</li> | 
						|
	 * </ul> | 
						|
	 * @return array the error details. Null if there is no error. | 
						|
	 */ | 
						|
	public function getError() | 
						|
	{ | 
						|
		return $this->_error; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Handles the PHP error. | 
						|
	 * @param CErrorEvent $event the PHP error event | 
						|
	 */ | 
						|
	protected function handleError($event) | 
						|
	{ | 
						|
		$trace = debug_backtrace(); | 
						|
		// skip the first 3 stacks as they do not tell the error position | 
						|
		if (count($trace) > 3) | 
						|
			$trace = array_slice($trace, 3); | 
						|
		$traceString = ''; | 
						|
		foreach ($trace as $i => $t) | 
						|
		{ | 
						|
			if (!isset($t['file'])) | 
						|
				$trace[$i]['file'] = 'unknown'; | 
						|
 | 
						|
			if (!isset($t['line'])) | 
						|
				$trace[$i]['line'] = 0; | 
						|
 | 
						|
			if (!isset($t['function'])) | 
						|
				$trace[$i]['function'] = 'unknown'; | 
						|
 | 
						|
			$traceString .= "#$i {$trace[$i]['file']}({$trace[$i]['line']}): "; | 
						|
			if (isset($t['object']) && is_object($t['object'])) | 
						|
				$traceString .= get_class($t['object']) . '->'; | 
						|
			$traceString .= "{$trace[$i]['function']}()\n"; | 
						|
 | 
						|
			unset($trace[$i]['object']); | 
						|
		} | 
						|
 | 
						|
		$app = Yii::app(); | 
						|
		if ($app instanceof CWebApplication) { | 
						|
			switch ($event->code) | 
						|
			{ | 
						|
				case E_WARNING: | 
						|
					$type = 'PHP warning'; | 
						|
					break; | 
						|
				case E_NOTICE: | 
						|
					$type = 'PHP notice'; | 
						|
					break; | 
						|
				case E_USER_ERROR: | 
						|
					$type = 'User error'; | 
						|
					break; | 
						|
				case E_USER_WARNING: | 
						|
					$type = 'User warning'; | 
						|
					break; | 
						|
				case E_USER_NOTICE: | 
						|
					$type = 'User notice'; | 
						|
					break; | 
						|
				case E_RECOVERABLE_ERROR: | 
						|
					$type = 'Recoverable error'; | 
						|
					break; | 
						|
				default: | 
						|
					$type = 'PHP error'; | 
						|
			} | 
						|
			$this->_error = $data = array( | 
						|
				'code' => 500, | 
						|
				'type' => $type, | 
						|
				'message' => $event->message, | 
						|
				'file' => $event->file, | 
						|
				'line' => $event->line, | 
						|
				'trace' => $traceString, | 
						|
				'traces' => $trace, | 
						|
			); | 
						|
			if (!headers_sent()) | 
						|
				header("HTTP/1.0 500 PHP Error"); | 
						|
			if ($this->isAjaxRequest()) | 
						|
				$app->displayError($event->code, $event->message, $event->file, $event->line); | 
						|
			else if (YII_DEBUG) | 
						|
				$this->render('exception', $data); | 
						|
			else | 
						|
				$this->render('error', $data); | 
						|
		} | 
						|
		else | 
						|
			$app->displayError($event->code, $event->message, $event->file, $event->line); | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Returns the exact trace where the problem occurs. | 
						|
	 * @param \Exception $exception the uncaught exception | 
						|
	 * @return array the exact trace where the problem occurs | 
						|
	 */ | 
						|
	protected function getExactTrace($exception) | 
						|
	{ | 
						|
		$traces = $exception->getTrace(); | 
						|
		foreach ($traces as $trace) { | 
						|
			// property access exception | 
						|
			if (isset($trace['function']) && ($trace['function'] === '__get' || $trace['function'] === '__set')) { | 
						|
				return $trace; | 
						|
			} | 
						|
		} | 
						|
		return null; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Renders the view. | 
						|
	 * @param string $view the view name (file name without extension). | 
						|
	 * See {@link getViewFile} for how a view file is located given its name. | 
						|
	 * @param array $data data to be passed to the view | 
						|
	 */ | 
						|
	protected function render($view, $data) | 
						|
	{ | 
						|
		if ($view === 'error' && $this->errorAction !== null) | 
						|
			Yii::app()->runController($this->errorAction); | 
						|
		else | 
						|
		{ | 
						|
			// additional information to be passed to view | 
						|
			$data['version'] = $this->getVersionInfo(); | 
						|
			$data['time'] = time(); | 
						|
			$data['admin'] = $this->adminInfo; | 
						|
			include($this->getViewFile($view, $data['code'])); | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Determines which view file should be used. | 
						|
	 * @param string $view view name (either 'exception' or 'error') | 
						|
	 * @param integer $code HTTP status code | 
						|
	 * @return string view file path | 
						|
	 */ | 
						|
	protected function getViewFile($view, $code) | 
						|
	{ | 
						|
		$viewPaths = array( | 
						|
			Yii::app()->getTheme() === null ? null : Yii::app()->getTheme()->getSystemViewPath(), | 
						|
			Yii::app() instanceof CWebApplication ? Yii::app()->getSystemViewPath() : null, | 
						|
			YII_PATH . DIRECTORY_SEPARATOR . 'views', | 
						|
		); | 
						|
 | 
						|
		foreach ($viewPaths as $i => $viewPath) | 
						|
		{ | 
						|
			if ($viewPath !== null) { | 
						|
				$viewFile = $this->getViewFileInternal($viewPath, $view, $code, $i === 2 ? 'en_us' : null); | 
						|
				if (is_file($viewFile)) | 
						|
					return $viewFile; | 
						|
			} | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Looks for the view under the specified directory. | 
						|
	 * @param string $viewPath the directory containing the views | 
						|
	 * @param string $view view name (either 'exception' or 'error') | 
						|
	 * @param integer $code HTTP status code | 
						|
	 * @param string $srcLanguage the language that the view file is in | 
						|
	 * @return string view file path | 
						|
	 */ | 
						|
	protected function getViewFileInternal($viewPath, $view, $code, $srcLanguage = null) | 
						|
	{ | 
						|
		$app = Yii::app(); | 
						|
		if ($view === 'error') { | 
						|
			$viewFile = $app->findLocalizedFile($viewPath . DIRECTORY_SEPARATOR . "error{$code}.php", $srcLanguage); | 
						|
			if (!is_file($viewFile)) | 
						|
				$viewFile = $app->findLocalizedFile($viewPath . DIRECTORY_SEPARATOR . 'error.php', $srcLanguage); | 
						|
		} | 
						|
		else | 
						|
			$viewFile = $viewPath . DIRECTORY_SEPARATOR . "exception.php"; | 
						|
		return $viewFile; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Returns server version information. | 
						|
	 * If the application is in production mode, empty string is returned. | 
						|
	 * @return string server version information. Empty if in production mode. | 
						|
	 */ | 
						|
	protected function getVersionInfo() | 
						|
	{ | 
						|
		if (YII_DEBUG) { | 
						|
			$version = '<a href="http://www.yiiframework.com/">Yii Framework</a>/' . \Yii::getVersion(); | 
						|
			if (isset($_SERVER['SERVER_SOFTWARE'])) { | 
						|
				$version = $_SERVER['SERVER_SOFTWARE'] . ' ' . $version; | 
						|
			} | 
						|
		} else { | 
						|
			$version = ''; | 
						|
		} | 
						|
		return $version; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Converts arguments array to its string representation | 
						|
	 * | 
						|
	 * @param array $args arguments array to be converted | 
						|
	 * @return string string representation of the arguments array | 
						|
	 */ | 
						|
	protected function argumentsToString($args) | 
						|
	{ | 
						|
		$count = 0; | 
						|
 | 
						|
		$isAssoc = $args !== array_values($args); | 
						|
 | 
						|
		foreach ($args as $key => $value) | 
						|
		{ | 
						|
			$count++; | 
						|
			if ($count >= 5) { | 
						|
				if ($count > 5) | 
						|
					unset($args[$key]); | 
						|
				else | 
						|
					$args[$key] = '...'; | 
						|
				continue; | 
						|
			} | 
						|
 | 
						|
			if (is_object($value)) | 
						|
				$args[$key] = get_class($value); | 
						|
			else if (is_bool($value)) | 
						|
				$args[$key] = $value ? 'true' : 'false'; | 
						|
			else if (is_string($value)) { | 
						|
				if (strlen($value) > 64) | 
						|
					$args[$key] = '"' . substr($value, 0, 64) . '..."'; | 
						|
				else | 
						|
					$args[$key] = '"' . $value . '"'; | 
						|
			} | 
						|
			else if (is_array($value)) | 
						|
				$args[$key] = 'array(' . $this->argumentsToString($value) . ')'; | 
						|
			else if ($value === null) | 
						|
				$args[$key] = 'null'; | 
						|
			else if (is_resource($value)) | 
						|
				$args[$key] = 'resource'; | 
						|
 | 
						|
			if (is_string($key)) { | 
						|
				$args[$key] = '"' . $key . '" => ' . $args[$key]; | 
						|
			} | 
						|
			else if ($isAssoc) { | 
						|
				$args[$key] = $key . ' => ' . $args[$key]; | 
						|
			} | 
						|
		} | 
						|
		$out = implode(", ", $args); | 
						|
 | 
						|
		return $out; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Returns a value indicating whether the call stack is from application code. | 
						|
	 * @param array $trace the trace data | 
						|
	 * @return boolean whether the call stack is from application code. | 
						|
	 */ | 
						|
	protected function isCoreCode($trace) | 
						|
	{ | 
						|
		if (isset($trace['file'])) { | 
						|
			$systemPath = realpath(dirname(__FILE__) . '/..'); | 
						|
			return $trace['file'] === 'unknown' || strpos(realpath($trace['file']), $systemPath . DIRECTORY_SEPARATOR) === 0; | 
						|
		} | 
						|
		return false; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Renders the source code around the error line. | 
						|
	 * @param string $file source file path | 
						|
	 * @param integer $errorLine the error line number | 
						|
	 * @param integer $maxLines maximum number of lines to display | 
						|
	 * @return string the rendering result | 
						|
	 */ | 
						|
	protected function renderSourceCode($file, $errorLine, $maxLines) | 
						|
	{ | 
						|
		$errorLine--; // adjust line number to 0-based from 1-based | 
						|
		if ($errorLine < 0 || ($lines = @file($file)) === false || ($lineCount = count($lines)) <= $errorLine) | 
						|
			return ''; | 
						|
 | 
						|
		$halfLines = (int)($maxLines / 2); | 
						|
		$beginLine = $errorLine - $halfLines > 0 ? $errorLine - $halfLines : 0; | 
						|
		$endLine = $errorLine + $halfLines < $lineCount ? $errorLine + $halfLines : $lineCount - 1; | 
						|
		$lineNumberWidth = strlen($endLine + 1); | 
						|
 | 
						|
		$output = ''; | 
						|
		for ($i = $beginLine; $i <= $endLine; ++$i) | 
						|
		{ | 
						|
			$isErrorLine = $i === $errorLine; | 
						|
			$code = sprintf("<span class=\"ln" . ($isErrorLine ? ' error-ln' : '') . "\">%0{$lineNumberWidth}d</span> %s", $i + 1, CHtml::encode(str_replace("\t", '    ', $lines[$i]))); | 
						|
			if (!$isErrorLine) | 
						|
				$output .= $code; | 
						|
			else | 
						|
				$output .= '<span class="error">' . $code . '</span>'; | 
						|
		} | 
						|
		return '<div class="code"><pre>' . $output . '</pre></div>'; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Handles PHP execution errors such as warnings, notices. | 
						|
	 * | 
						|
	 * This method is implemented as a PHP error handler. It requires | 
						|
	 * that constant YII_ENABLE_ERROR_HANDLER be defined true. | 
						|
	 * | 
						|
	 * This method will first raise an `error` event. | 
						|
	 * If the error is not handled by any event handler, it will call | 
						|
	 * {@link getErrorHandler errorHandler} to process the error. | 
						|
	 * | 
						|
	 * The application will be terminated by this method. | 
						|
	 * | 
						|
	 * @param integer $code the level of the error raised | 
						|
	 * @param string $message the error message | 
						|
	 * @param string $file the filename that the error was raised in | 
						|
	 * @param integer $line the line number the error was raised at | 
						|
	 */ | 
						|
	public function handleError($code, $message, $file, $line) | 
						|
	{ | 
						|
		throw new \ErrorException($message, 0, $code, $file, $line); | 
						|
	} | 
						|
}
 | 
						|
 |