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); | |
| 	} | |
| }
 | |
| 
 |