From 410986abf8157a3baed69a98afe90069d60c80a7 Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Tue, 6 Jun 2006 23:07:26 +0000 Subject: [PATCH] Some initial work on exceptions. More to follow. --- includes/Database.php | 279 ++++++++++++++++++++++++++------ includes/DatabaseOracle.php | 8 +- includes/DatabasePostgreSQL.php | 18 +-- includes/Exception.php | 172 ++++++++++++++++++++ includes/LoadBalancer.php | 5 +- includes/Setup.php | 6 + 6 files changed, 423 insertions(+), 65 deletions(-) create mode 100644 includes/Exception.php diff --git a/includes/Database.php b/includes/Database.php index 2bdf610baa..173bc712bd 100644 --- a/includes/Database.php +++ b/includes/Database.php @@ -24,8 +24,12 @@ define( 'DEADLOCK_DELAY_MIN', 500000 ); /** Maximum time to wait before retry */ define( 'DEADLOCK_DELAY_MAX', 1500000 ); +/****************************************************************************** + * Utility classes + *****************************************************************************/ + class DBObject { - var $mData; + public $mData; function DBObject($data) { $this->mData = $data; @@ -40,6 +44,190 @@ class DBObject { } }; +/****************************************************************************** + * Error classes + *****************************************************************************/ + +/** + * Database error base class + */ +class DBError extends MWException { + public $db; + + /** + * Construct a database error + * @param Database $db The database object which threw the error + * @param string $error A simple error message to be used for debugging + */ + function __construct( Database &$db, $error ) { + $this->db =& $db; + parent::__construct( $error ); + } +} + +class DBConnectionError extends DBError { + public $error; + + function __construct( Database &$db, $error = 'unknown error' ) { + $msg = 'DB connection error'; + if ( trim( $error ) != '' ) { + $msg .= ": $error"; + } + $this->error = $error; + parent::__construct( $db, $msg ); + } + + function useOutputPage() { + // Not likely to work + return false; + } + + function useMessageCache() { + // Not likely to work + return false; + } + + function getText() { + return $this->getMessage() . "\n"; + } + + function getPageTitle() { + global $wgSitename; + return "$wgSitename has a problem"; + } + + function getHTML() { + global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding, $wgOutputEncoding; + global $wgSitename, $wgServer, $wgMessageCache, $wgLogo; + + # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky. + # Hard coding strings instead. + + $noconnect = "

Sorry! This site is experiencing technical difficulties.

Try waiting a few minutes and reloading.

(Can't contact the database server: $1)

"; + $mainpage = 'Main Page'; + $searchdisabled = <<$wgSitename search is disabled for performance reasons. You can search via Google in the meantime. +Note that their indexes of $wgSitename content may be out of date.

', +EOT; + + $googlesearch = " + +
+ +
+ +\"Google\" + + + + +
WWW $wgServer
+ + +
+
+
+"; + $cachederror = "The following is a cached copy of the requested page, and may not be up to date. "; + + # No database access + if ( is_object( $wgMessageCache ) ) { + $wgMessageCache->disable(); + } + + if ( trim( $this->error ) == '' ) { + $this->error = $this->db->getProperty('mServer'); + } + + $text = str_replace( '$1', $this->error, $noconnect ); + $text .= wfGetSiteNotice(); + + if($wgUseFileCache) { + if($wgTitle) { + $t =& $wgTitle; + } else { + if($title) { + $t = Title::newFromURL( $title ); + } elseif (@/**/$_REQUEST['search']) { + $search = $_REQUEST['search']; + return $searchdisabled . + str_replace( array( '$1', '$2' ), array( htmlspecialchars( $search ), + $wgInputEncoding ), $googlesearch ); + } else { + $t = Title::newFromText( $mainpage ); + } + } + + $cache = new CacheManager( $t ); + if( $cache->isFileCached() ) { + $msg = '

'.$msg."
\n" . + $cachederror . "

\n"; + + $tag = '
'; + $text = str_replace( + $tag, + $tag . $msg, + $cache->fetchPageText() ); + } + } + + return $text; + } +} + +class DBQueryError extends DBError { + public $error, $errno, $sql, $fname; + + function __construct( Database &$db, $error, $errno, $sql, $fname ) { + $message = "A database error has occurred\n" . + "Query: $sql\n" . + "Function: $fname\n" . + "Error: $errno $error\n"; + + parent::__construct( $db, $message ); + $this->error = $error; + $this->errno = $errno; + $this->sql = $sql; + $this->fname = $fname; + } + + function getText() { + if ( $this->useMessageCache() ) { + return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ), + htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n"; + } else { + return $this->getMessage(); + } + } + + function getSQL() { + global $wgShowSQLErrors; + if( !$wgShowSQLErrors ) { + return $this->msg( 'sqlhidden', 'SQL hidden' ); + } else { + return $this->sql; + } + } + + function getPageTitle() { + return $this->msg( 'databaseerror', 'Database error' ); + } + + function getHTML() { + if ( $this->useMessageCache() ) { + return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ), + htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ); + } else { + return nl2br( htmlspecialchars( $this->getMessage() ) ); + } + } +} + +class DBUnexpectedError extends DBError {} + +/******************************************************************************/ + /** * Database abstraction object * @package MediaWiki @@ -49,21 +237,18 @@ class Database { #------------------------------------------------------------------------------ # Variables #------------------------------------------------------------------------------ - /**#@+ - * @private - */ - var $mLastQuery = ''; - var $mServer, $mUser, $mPassword, $mConn = null, $mDBname; - var $mOut, $mOpened = false; + protected $mLastQuery = ''; - var $mFailFunction; - var $mTablePrefix; - var $mFlags; - var $mTrxLevel = 0; - var $mErrorCount = 0; - var $mLBInfo = array(); - /**#@-*/ + protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; + protected $mOut, $mOpened = false; + + protected $mFailFunction; + protected $mTablePrefix; + protected $mFlags; + protected $mTrxLevel = 0; + protected $mErrorCount = 0; + protected $mLBInfo = array(); #------------------------------------------------------------------------------ # Accessors @@ -173,6 +358,13 @@ class Database { return !!($this->mFlags & $flag); } + /** + * General read-only accessor + */ + function getProperty( $name ) { + return $this->$name; + } + #------------------------------------------------------------------------------ # Other functions #------------------------------------------------------------------------------ @@ -189,7 +381,7 @@ class Database { * @param $flags * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php */ - function Database( $server = false, $user = false, $password = false, $dbName = false, + function __construct( $server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) { global $wgOut, $wgDBprefix, $wgCommandLineMode; @@ -234,7 +426,7 @@ class Database { * @param failFunction * @param $flags */ - function newFromParams( $server, $user, $password, $dbName, + static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) { return new Database( $server, $user, $password, $dbName, $failFunction, $flags ); @@ -253,9 +445,10 @@ class Database { @dl('mysql.so'); } + # Fail now # Otherwise we get a suppressed fatal error, which is very hard to track down if ( !function_exists( 'mysql_connect' ) ) { - wfDie( "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" ); + throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" ); } $this->close(); @@ -328,7 +521,6 @@ class Database { } /** - * @private * @param string $error fallback error message, used if none is given by MySQL */ function reportConnectionError( $error = 'Unknown error' ) { @@ -338,12 +530,15 @@ class Database { } if ( $this->mFailFunction ) { + # Legacy error handling method if ( !is_int( $this->mFailFunction ) ) { $ff = $this->mFailFunction; $ff( $this, $error ); } } else { - wfEmergencyAbort( $this, $error ); + # New method + wfLogDBError( "Connection error: $error\n" ); + throw new DBConnectionError( $this, $error ); } } @@ -447,29 +642,13 @@ class Database { if( $ignore || $tempIgnore ) { wfDebug("SQL ERROR (ignored): $error\n"); + $this->ignoreErrors( $ignore ); } else { $sql1line = str_replace( "\n", "\\n", $sql ); wfLogDBError("$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n"); wfDebug("SQL ERROR: " . $error . "\n"); - if ( $wgCommandLineMode || !$this->mOut || empty( $wgFullyInitialised ) ) { - $message = "A database error has occurred\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - if ( !$wgCommandLineMode ) { - $message = nl2br( $message ); - } - if( $wgCommandLineMode && $wgColorErrors && !wfIsWindows() && posix_isatty(1) ) { - $color = 31; // bright red! - $message = "\x1b[1;{$color}m{$message}\x1b[0m"; - } - wfDebugDieBacktrace( $message ); - } else { - // this calls wfAbruptExit() - $this->mOut->databaseError( $fname, $sql, $error, $errno ); - } + throw new DBQueryError( $this, $error, $errno, $sql, $fname ); } - $this->ignoreErrors( $ignore ); } @@ -561,9 +740,9 @@ class Database { case '!': return $arg; case '&': # return $this->addQuotes( file_get_contents( $arg ) ); - wfDebugDieBacktrace( '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); + throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); default: - wfDebugDieBacktrace( 'Received invalid match. This should never happen!' ); + throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); } } @@ -575,7 +754,7 @@ class Database { */ function freeResult( $res ) { if ( !@/**/mysql_free_result( $res ) ) { - wfDebugDieBacktrace( "Unable to free MySQL result\n" ); + throw new DBUnexpectedError( $this, "Unable to free MySQL result" ); } } @@ -585,7 +764,7 @@ class Database { function fetchObject( $res ) { @/**/$row = mysql_fetch_object( $res ); if( mysql_errno() ) { - wfDebugDieBacktrace( 'Error in fetchObject(): ' . htmlspecialchars( mysql_error() ) ); + throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( mysql_error() ) ); } return $row; } @@ -597,7 +776,7 @@ class Database { function fetchRow( $res ) { @/**/$row = mysql_fetch_array( $res ); if (mysql_errno() ) { - wfDebugDieBacktrace( 'Error in fetchRow(): ' . htmlspecialchars( mysql_error() ) ); + throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( mysql_error() ) ); } return $row; } @@ -608,7 +787,7 @@ class Database { function numRows( $res ) { @/**/$n = mysql_num_rows( $res ); if( mysql_errno() ) { - wfDebugDieBacktrace( 'Error in numRows(): ' . htmlspecialchars( mysql_error() ) ); + throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( mysql_error() ) ); } return $n; } @@ -847,7 +1026,7 @@ class Database { * @param string $sql A SQL Query * @static */ - function generalizeSQL( $sql ) { + static function generalizeSQL( $sql ) { # This does the same as the regexp below would do, but in such a way # as to avoid crashing php on some large strings. # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); @@ -1079,7 +1258,7 @@ class Database { */ function makeList( $a, $mode = LIST_COMMA ) { if ( !is_array( $a ) ) { - wfDebugDieBacktrace( 'Database::makeList called with incorrect parameters' ); + throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); } $first = true; @@ -1282,7 +1461,7 @@ class Database { */ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) { if ( !$conds ) { - wfDebugDieBacktrace( 'Database::deleteJoin() called with empty $conds' ); + throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' ); } $delTable = $this->tableName( $delTable ); @@ -1327,7 +1506,7 @@ class Database { */ function delete( $table, $conds, $fname = 'Database::delete' ) { if ( !$conds ) { - wfDebugDieBacktrace( 'Database::delete() called with no conditions' ); + throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' ); } $table = $this->tableName( $table ); $sql = "DELETE FROM $table"; @@ -1379,7 +1558,7 @@ class Database { */ function limitResult($sql, $limit, $offset=false) { if( !is_numeric($limit) ) { - wfDie( "Invalid non-numeric limit passed to limitResult()\n" ); + throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); } return " $sql LIMIT " . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" ) @@ -1737,7 +1916,7 @@ class Database { /** * Replace variables in sourced SQL */ - function replaceVars( $ins ) { + protected function replaceVars( $ins ) { $varnames = array( 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser', 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword', @@ -1764,7 +1943,7 @@ class Database { * Table name callback * @private */ - function tableNameCallback( $matches ) { + protected function tableNameCallback( $matches ) { return $this->tableName( $matches[1] ); } diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php index 226bb4d6bd..d5d7379dc6 100644 --- a/includes/DatabaseOracle.php +++ b/includes/DatabaseOracle.php @@ -52,7 +52,7 @@ class DatabaseOracle extends Database { */ function open( $server, $user, $password, $dbName ) { if ( !function_exists( 'oci_connect' ) ) { - wfDie( "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n" ); + throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n" ); } $this->close(); $this->mServer = $server; @@ -137,7 +137,7 @@ class DatabaseOracle extends Database { function freeResult( $res ) { if (!oci_free_statement($res)) { - wfDebugDieBacktrace( "Unable to free Oracle result\n" ); + throw new DBUnexpectedError( $this, "Unable to free Oracle result\n" ); } unset($this->mFetchID[$res]); unset($this->mFetchCache[$res]); @@ -385,7 +385,7 @@ class DatabaseOracle extends Database { # DELETE where the condition is a join function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) { if ( !$conds ) { - wfDebugDieBacktrace( 'Database::deleteJoin() called with empty $conds' ); + throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' ); } $delTable = $this->tableName( $delTable ); @@ -467,7 +467,7 @@ class DatabaseOracle extends Database { "Query: $sql\n" . "Function: $fname\n" . "Error: $errno $error\n"; - wfDebugDieBacktrace($message); + throw new DBUnexpectedError($this, $message); } /** diff --git a/includes/DatabasePostgreSQL.php b/includes/DatabasePostgreSQL.php index 130a247a85..23f82c25ae 100644 --- a/includes/DatabasePostgreSQL.php +++ b/includes/DatabasePostgreSQL.php @@ -45,7 +45,7 @@ class DatabasePgsql extends Database { function open( $server, $user, $password, $dbName ) { # Test for PostgreSQL support, to avoid suppressed fatal error if ( !function_exists( 'pg_connect' ) ) { - wfDie( "PostgreSQL functions missing, have you compiled PHP with the --with-pgsql option?\n" ); + throw new DBConnectionError( $this, "PostgreSQL functions missing, have you compiled PHP with the --with-pgsql option?\n" ); } global $wgDBschema; @@ -101,7 +101,7 @@ class DatabasePgsql extends Database { function freeResult( $res ) { if ( !@pg_free_result( $res ) ) { - wfDebugDieBacktrace( "Unable to free PostgreSQL result\n" ); + throw new DBUnexpectedError($this, "Unable to free PostgreSQL result\n" ); } } @@ -113,7 +113,7 @@ class DatabasePgsql extends Database { # hashar : not sure if the following test really trigger if the object # fetching failled. if( pg_last_error($this->mConn) ) { - wfDebugDieBacktrace( 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); + throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); } return $row; } @@ -121,7 +121,7 @@ class DatabasePgsql extends Database { function fetchRow( $res ) { @$row = pg_fetch_array( $res ); if( pg_last_error($this->mConn) ) { - wfDebugDieBacktrace( 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); + throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); } return $row; } @@ -129,7 +129,7 @@ class DatabasePgsql extends Database { function numRows( $res ) { @$n = pg_num_rows( $res ); if( pg_last_error($this->mConn) ) { - wfDebugDieBacktrace( 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); + throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); } return $n; } @@ -183,13 +183,13 @@ class DatabasePgsql extends Database { } function fieldInfo( $table, $field ) { - wfDebugDieBacktrace( 'Database::fieldInfo() error : mysql_fetch_field() not implemented for postgre' ); + throw new DBUnexpectedError($this, 'Database::fieldInfo() error : mysql_fetch_field() not implemented for postgre' ); /* $res = $this->query( "SELECT * FROM '$table' LIMIT 1" ); $n = pg_num_fields( $res ); for( $i = 0; $i < $n; $i++ ) { // FIXME - wfDebugDieBacktrace( "Database::fieldInfo() error : mysql_fetch_field() not implemented for postgre" ); + throw new DBUnexpectedError($this, "Database::fieldInfo() error : mysql_fetch_field() not implemented for postgre" ); $meta = mysql_fetch_field( $res, $i ); if( $field == $meta->name ) { return $meta; @@ -328,7 +328,7 @@ class DatabasePgsql extends Database { # DELETE where the condition is a join function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) { if ( !$conds ) { - wfDebugDieBacktrace( 'Database::deleteJoin() called with empty $conds' ); + throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); } $delTable = $this->tableName( $delTable ); @@ -404,7 +404,7 @@ class DatabasePgsql extends Database { "Query: $sql\n" . "Function: $fname\n" . "Error: $errno $error\n"; - wfDebugDieBacktrace($message); + throw new DBUnexpectedError($this, $message); } /** diff --git a/includes/Exception.php b/includes/Exception.php new file mode 100644 index 0000000000..11bb5971d5 --- /dev/null +++ b/includes/Exception.php @@ -0,0 +1,172 @@ +useMessageCache() ) { + return wfMsgReal( $key, $args ); + } else { + return wfMsgReplaceArgs( $fallback, $args ); + } + } + + function getHTML() { + return '

' . htmlspecialchars( $this->getMessage() ) . + '

Backtrace:

' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . + "

\n"; + } + + function getText() { + return $this->getMessage() . + "\nBacktrace:\n" . $this->getTraceAsString() . "\n"; + } + + function getPageTitle() { + if ( $this->useMessageCache() ) { + return wfMsg( 'internalerror' ); + } else { + global $wgSitename; + return "$wgSitename error"; + } + } + + function reportHTML() { + global $wgOut; + if ( $this->useOutputPage() ) { + $wgOut->setPageTitle( $this->getPageTitle() ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->setArticleRelated( false ); + $wgOut->enableClientCache( false ); + $wgOut->redirect( '' ); + $wgOut->clearHTML(); + $wgOut->addHTML( $this->getHTML() ); + $wgOut->output(); + } else { + echo $this->htmlHeader(); + echo $this->getHTML(); + echo $this->htmlFooter(); + } + } + + function reportText() { + echo $this->getText(); + } + + function report() { + global $wgCommandLineMode; + if ( $wgCommandLineMode ) { + $this->reportText(); + } else { + $this->reportHTML(); + } + } + + function htmlHeader() { + global $wgLogo, $wgSitename, $wgOutputEncoding; + + if ( !headers_sent() ) { + header( 'HTTP/1.0 500 Internal Server Error' ); + header( 'Content-type: text/html; charset='.$wgOutputEncoding ); + /* Don't cache error pages! They cause no end of trouble... */ + header( 'Cache-control: none' ); + header( 'Pragma: nocache' ); + } + $title = $this->getPageTitle(); + echo " + + $title + + +

$title

+ "; + } + + function htmlFooter() { + echo ""; + } +} + +/** + * Exception class which takes an HTML error message, and does not + * produce a backtrace. Replacement for OutputPage::fatalError(). + */ +class FatalError extends MWException { + function getHTML() { + return $this->getMessage(); + } + + function getText() { + return $this->getMessage(); + } +} + +/** + * Install an exception handler for MediaWiki exception types. + */ +function wfInstallExceptionHandler() { + set_exception_handler( 'wfExceptionHandler' ); +} + +/** + * Report an exception to the user + */ +function wfReportException( Exception $e ) { + if ( is_a( $e, 'MWException' ) ) { + try { + $e->report(); + } catch ( Exception $e2 ) { + // Exception occurred from within exception handler + // Show a simpler error message for the original exception, + // don't try to invoke report() + $message = "MediaWiki internal error.\n\n" . + "Original exception: " . $e->__toString() . + "\n\nException caught inside exception handler: " . + $e2->__toString() . "\n"; + + if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) { + echo $message; + } else { + echo nl2br( htmlspecialchars( $message ) ). "\n"; + } + } + } else { + echo $e->__toString(); + } +} + +/** + * Exception handler which simulates the appropriate catch() handling: + * + * try { + * ... + * } catch ( MWException $e ) { + * + * $e->report(); + * } catch ( Exception $e ) { + * echo $e->__toString(); + * } + */ +function wfExceptionHandler( $e ) { + wfReportException( $e ); + + // Final cleanup, similar to wfErrorExit() + try { + wfProfileClose(); + logProfilingData(); + } catch ( Exception $e ) {} + + // Exit value should be nonzero for the benefit of shell jobs + exit( 1 ); +} + +?> diff --git a/includes/LoadBalancer.php b/includes/LoadBalancer.php index d77cf3f66a..dada8d44fa 100644 --- a/includes/LoadBalancer.php +++ b/includes/LoadBalancer.php @@ -436,7 +436,7 @@ class LoadBalancer { */ function reallyOpenConnection( &$server ) { if( !is_array( $server ) ) { - wfDebugDieBacktrace( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' ); + throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' ); } extract( $server ); @@ -477,7 +477,8 @@ class LoadBalancer { } else { $conn->failFunction( false ); } - $conn->reportConnectionError( "{$this->mLastError} ({$conn->mServer})" ); + $server = $conn->getProperty( 'mServer' ); + $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); } $reporting = false; } diff --git a/includes/Setup.php b/includes/Setup.php index 423022698b..b5480d65f2 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -43,6 +43,12 @@ if ( function_exists( 'wfProfileIn' ) ) { $fname = 'Setup.php'; wfProfileIn( $fname ); + +wfProfileIn( $fname.'-exception' ); +require_once( 'Exception.php' ); +wfInstallExceptionHandler(); +wfProfileOut( $fname.'-exception' ); + wfProfileIn( $fname.'-includes' ); require_once( 'AutoLoader.php' ); -- 2.20.1