Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TErrorHandler class file** @author Qiang Xue <qiang.xue@gmail.com>* @link http://www.pradosoft.com/* @copyright Copyright © 2005-2008 PradoSoft* @license http://www.pradosoft.com/license/* @version $Id: TErrorHandler.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System.Exceptions*//*** TErrorHandler class** TErrorHandler handles all PHP user errors and exceptions generated during* servicing user requests. It displays these errors using different templates* and if possible, using languages preferred by the client user.* Note, PHP parsing errors cannot be caught and handled by TErrorHandler.** The templates used to format the error output are stored under System.Exceptions.* You may choose to use your own templates, should you not like the templates* provided by Prado. Simply set {@link setErrorTemplatePath ErrorTemplatePath}* to the path (in namespace format) storing your own templates.** There are two sets of templates, one for errors to be displayed to client users* (called external errors), one for errors to be displayed to system developers* (called internal errors). The template file name for the former is* <b>error[StatusCode][-LanguageCode].html</b>, and for the latter it is* <b>exception[-LanguageCode].html</b>, where StatusCode refers to response status* code (e.g. 404, 500) specified when {@link THttpException} is thrown,* and LanguageCode is the client user preferred language code (e.g. en, zh, de).* The templates <b>error.html</b> and <b>exception.html</b> are default ones* that are used if no other appropriate templates are available.* Note, these templates are not Prado control templates. They are simply* html files with keywords (e.g. %%ErrorMessage%%, %%Version%%)* to be replaced with the corresponding information.** By default, TErrorHandler is registered with {@link TApplication} as the* error handler module. It can be accessed via {@link TApplication::getErrorHandler()}.* You seldom need to deal with the error handler directly. It is mainly used* by the application object to handle errors.** TErrorHandler may be configured in application configuration file as follows* <module id="error" class="TErrorHandler" ErrorTemplatePath="System.Exceptions" />** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TErrorHandler.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System.Exceptions* @since 3.0*/class TErrorHandler extends TModule{/*** error template file basename*/const ERROR_FILE_NAME='error';/*** exception template file basename*/const EXCEPTION_FILE_NAME='exception';/*** number of lines before and after the error line to be displayed in case of an exception*/const SOURCE_LINES=12;/*** @var string error template directory*/private $_templatePath=null;/*** Initializes the module.* This method is required by IModule and is invoked by application.* @param TXmlElement module configuration*/public function init($config){$this->getApplication()->setErrorHandler($this);}/*** @return string the directory containing error template files.*/public function getErrorTemplatePath(){if($this->_templatePath===null)$this->_templatePath=Prado::getFrameworkPath().'/Exceptions/templates';return $this->_templatePath;}/*** Sets the path storing all error and exception template files.* The path must be in namespace format, such as System.Exceptions (which is the default).* @param string template path in namespace format* @throws TConfigurationException if the template path is invalid*/public function setErrorTemplatePath($value){if(($templatePath=Prado::getPathOfNamespace($value))!==null && is_dir($templatePath))$this->_templatePath=$templatePath;elsethrow new TConfigurationException('errorhandler_errortemplatepath_invalid',$value);}/*** Handles PHP user errors and exceptions.* This is the event handler responding to the <b>Error</b> event* raised in {@link TApplication}.* The method mainly uses appropriate template to display the error/exception.* It terminates the application immediately after the error is displayed.* @param mixed sender of the event* @param mixed event parameter (if the event is raised by TApplication, it refers to the exception instance)*/public function handleError($sender,$param){static $handling=false;// We need to restore error and exception handlers,// because within error and exception handlers, new errors and exceptions// cannot be handled properly by PHPrestore_error_handler();restore_exception_handler();// ensure that we do not enter infinite loop of error handlingif($handling)$this->handleRecursiveError($param);else{$handling=true;if(($response=$this->getResponse())!==null)$response->clear();if(!headers_sent())header('Content-Type: text/html; charset=UTF-8');if($param instanceof THttpException)$this->handleExternalError($param->getStatusCode(),$param);else if($this->getApplication()->getMode()===TApplicationMode::Debug)$this->displayException($param);else$this->handleExternalError(500,$param);}}/*** Displays error to the client user.* THttpException and errors happened when the application is in <b>Debug</b>* mode will be displayed to the client user.* @param integer response status code* @param Exception exception instance*/protected function handleExternalError($statusCode,$exception){if(!($exception instanceof THttpException))error_log($exception->__toString());$content=$this->getErrorTemplate($statusCode,$exception);$serverAdmin=isset($_SERVER['SERVER_ADMIN'])?$_SERVER['SERVER_ADMIN']:'';if($this->getApplication()->getMode()===TApplicationMode::Debug)$version=$_SERVER['SERVER_SOFTWARE'].' <a href="http://www.pradosoft.com/">PRADO</a>/'.Prado::getVersion();else$version='';$tokens=array('%%StatusCode%%' => "$statusCode",'%%ErrorMessage%%' => htmlspecialchars($exception->getMessage()),'%%ServerAdmin%%' => $serverAdmin,'%%Version%%' => $version,'%%Time%%' => @strftime('%Y-%m-%d %H:%M',time()));header("HTTP/1.0 $statusCode ".$exception->getMessage());echo strtr($content,$tokens);}/*** Handles error occurs during error handling (called recursive error).* THttpException and errors happened when the application is in <b>Debug</b>* mode will be displayed to the client user.* Error is displayed without using existing template to prevent further errors.* @param Exception exception instance*/protected function handleRecursiveError($exception){if($this->getApplication()->getMode()===TApplicationMode::Debug){echo "<html><head><title>Recursive Error</title></head>\n";echo "<body><h1>Recursive Error</h1>\n";echo "<pre>".$exception->__toString()."</pre>\n";echo "</body></html>";}else{error_log("Error happened while processing an existing error:\n".$exception->__toString());header('HTTP/1.0 500 Internal Error');}}/*** Displays exception information.* Exceptions are displayed with rich context information, including* the call stack and the context source code.* This method is only invoked when application is in <b>Debug</b> mode.* @param Exception exception instance*/protected function displayException($exception){if(php_sapi_name()==='cli'){echo $exception->getMessage()."\n";echo $exception->getTraceAsString();return;}if($exception instanceof TTemplateException){$fileName=$exception->getTemplateFile();$lines=empty($fileName)?explode("\n",$exception->getTemplateSource()):@file($fileName);$source=$this->getSourceCode($lines,$exception->getLineNumber());if($fileName==='')$fileName='---embedded template---';$errorLine=$exception->getLineNumber();}else{if(($trace=$this->getExactTrace($exception))!==null){$fileName=$trace['file'];$errorLine=$trace['line'];}else{$fileName=$exception->getFile();$errorLine=$exception->getLine();}$source=$this->getSourceCode(@file($fileName),$errorLine);}if($this->getApplication()->getMode()===TApplicationMode::Debug)$version=$_SERVER['SERVER_SOFTWARE'].' <a href="http://www.pradosoft.com/">PRADO</a>/'.Prado::getVersion();else$version='';$tokens=array('%%ErrorType%%' => get_class($exception),'%%ErrorMessage%%' => $this->addLink(htmlspecialchars($exception->getMessage())),'%%SourceFile%%' => htmlspecialchars($fileName).' ('.$errorLine.')','%%SourceCode%%' => $source,'%%StackTrace%%' => htmlspecialchars($exception->getTraceAsString()),'%%Version%%' => $version,'%%Time%%' => @strftime('%Y-%m-%d %H:%M',time()));$content=$this->getExceptionTemplate($exception);echo strtr($content,$tokens);}/*** Retrieves the template used for displaying internal exceptions.* Internal exceptions will be displayed with source code causing the exception.* This occurs when the application is in debug mode.* @param Exception the exception to be displayed* @return string the template content*/protected function getExceptionTemplate($exception){$lang=Prado::getPreferredLanguage();$exceptionFile=Prado::getFrameworkPath().'/Exceptions/templates/'.self::EXCEPTION_FILE_NAME.'-'.$lang.'.html';if(!is_file($exceptionFile))$exceptionFile=Prado::getFrameworkPath().'/Exceptions/templates/'.self::EXCEPTION_FILE_NAME.'.html';if(($content=@file_get_contents($exceptionFile))===false)die("Unable to open exception template file '$exceptionFile'.");return $content;}/*** Retrieves the template used for displaying external exceptions.* External exceptions are those displayed to end-users. They do not contain* error source code. Therefore, you might want to override this method* to provide your own error template for displaying certain external exceptions.* The following tokens in the template will be replaced with corresponding content:* %%StatusCode%% : the status code of the exception* %%ErrorMessage%% : the error message (HTML encoded).* %%ServerAdmin%% : the server admin information (retrieved from Web server configuration)* %%Version%% : the version information of the Web server.* %%Time%% : the time the exception occurs at** @param integer status code (such as 404, 500, etc.)* @param Exception the exception to be displayed* @return string the template content*/protected function getErrorTemplate($statusCode,$exception){$base=$this->getErrorTemplatePath().DIRECTORY_SEPARATOR.self::ERROR_FILE_NAME;$lang=Prado::getPreferredLanguage();if(is_file("$base$statusCode-$lang.html"))$errorFile="$base$statusCode-$lang.html";else if(is_file("$base$statusCode.html"))$errorFile="$base$statusCode.html";else if(is_file("$base-$lang.html"))$errorFile="$base-$lang.html";else$errorFile="$base.html";if(($content=@file_get_contents($errorFile))===false)die("Unable to open error template file '$errorFile'.");return $content;}private function getExactTrace($exception){$trace=$exception->getTrace();$result=null;// if PHP exception, we want to show the 2nd stack level context// because the 1st stack level is of little use (it's in error handler)if($exception instanceof TPhpErrorException)$result=isset($trace[0]['file'])?$trace[0]:$trace[1];else if($exception instanceof TInvalidOperationException){// in case of getter or setter error, find out the exact file and rowif(($result=$this->getPropertyAccessTrace($trace,'__get'))===null)$result=$this->getPropertyAccessTrace($trace,'__set');}if($result!==null && strpos($result['file'],': eval()\'d code')!==false)return null;return $result;}private function getPropertyAccessTrace($trace,$pattern){$result=null;foreach($trace as $t){if(isset($t['function']) && $t['function']===$pattern)$result=$t;elsebreak;}return $result;}private function getSourceCode($lines,$errorLine){$beginLine=$errorLine-self::SOURCE_LINES>=0?$errorLine-self::SOURCE_LINES:0;$endLine=$errorLine+self::SOURCE_LINES<=count($lines)?$errorLine+self::SOURCE_LINES:count($lines);$source='';for($i=$beginLine;$i<$endLine;++$i){if($i===$errorLine-1){$line=htmlspecialchars(sprintf("%04d: %s",$i+1,str_replace("\t",' ',$lines[$i])));$source.="<div class=\"error\">".$line."</div>";}else$source.=htmlspecialchars(sprintf("%04d: %s",$i+1,str_replace("\t",' ',$lines[$i])));}return $source;}private function addLink($message){$baseUrl='http://www.pradosoft.com/docs/classdoc';return preg_replace('/\b(T[A-Z]\w+)\b/',"<a href=\"$baseUrl/\${1}\" target=\"_blank\">\${1}</a>",$message);}}