- Patch #304924 by Damien Tournoud: extend error handler to manage exceptions. I have one exception and one fail.
parent
e85187d948
commit
967d8f67ac
|
@ -371,6 +371,10 @@ function drupal_initialize_variables() {
|
|||
if (!isset($_SERVER['SERVER_PROTOCOL']) || ($_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.0' && $_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.1')) {
|
||||
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0';
|
||||
}
|
||||
// Enforce E_ALL, but allow users to set levels not part of E_ALL.
|
||||
error_reporting(E_ALL | error_reporting());
|
||||
// Prevent PHP from generating HTML errors messages.
|
||||
ini_set('html_errors', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -583,55 +583,122 @@ function drupal_http_request($url, $headers = array(), $method = 'GET', $data =
|
|||
*/
|
||||
|
||||
/**
|
||||
* Log errors as defined by administrator.
|
||||
* Custom PHP error handler.
|
||||
*
|
||||
* Error levels:
|
||||
* - 0 = Log errors to database.
|
||||
* - 1 = Log errors to database and to screen.
|
||||
* @param $error_level
|
||||
* The level of the error raised.
|
||||
* @param $message
|
||||
* The error message.
|
||||
* @param $filename
|
||||
* The filename that the error was raised in.
|
||||
* @param $line
|
||||
* The line number the error was raised at.
|
||||
* @param $context
|
||||
* An array that points to the active symbol table at the point the error occurred.
|
||||
*/
|
||||
function drupal_error_handler($errno, $message, $filename, $line, $context) {
|
||||
// If the @ error suppression operator was used, error_reporting will have
|
||||
// been temporarily set to 0.
|
||||
if (error_reporting() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($errno & (E_ALL)) {
|
||||
$types = array(1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning', 4096 => 'recoverable fatal error');
|
||||
|
||||
// For database errors, we want the line number/file name of the place that
|
||||
// the query was originally called, not _db_query().
|
||||
if (isset($context[DB_ERROR])) {
|
||||
$backtrace = array_reverse(debug_backtrace());
|
||||
|
||||
// List of functions where SQL queries can originate.
|
||||
$query_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
|
||||
|
||||
// Determine where query function was called, and adjust line/file
|
||||
// accordingly.
|
||||
foreach ($backtrace as $index => $function) {
|
||||
if (in_array($function['function'], $query_functions)) {
|
||||
$line = $backtrace[$index]['line'];
|
||||
$filename = $backtrace[$index]['file'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entry = $types[$errno] . ': ' . $message . ' in ' . $filename . ' on line ' . $line . '.';
|
||||
|
||||
// Force display of error messages in update.php.
|
||||
if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) {
|
||||
drupal_set_message($entry, 'error');
|
||||
}
|
||||
|
||||
watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
|
||||
function _drupal_error_handler($error_level, $message, $filename, $line, $context) {
|
||||
if ($error_level & error_reporting()) {
|
||||
// All these constants are documented at http://php.net/manual/en/errorfunc.constants.php
|
||||
$types = array(
|
||||
E_ERROR => 'Error',
|
||||
E_WARNING => 'Warning',
|
||||
E_PARSE => 'Parse error',
|
||||
E_NOTICE => 'Notice',
|
||||
E_CORE_ERROR => 'Core error',
|
||||
E_CORE_WARNING => 'Core warning',
|
||||
E_COMPILE_ERROR => 'Compile error',
|
||||
E_COMPILE_WARNING => 'Compile warning',
|
||||
E_USER_ERROR => 'User error',
|
||||
E_USER_WARNING => 'User warning',
|
||||
E_USER_NOTICE => 'User notice',
|
||||
E_STRICT => 'Strict warning',
|
||||
E_RECOVERABLE_ERROR => 'Recoverable fatal error'
|
||||
);
|
||||
$backtrace = debug_backtrace();
|
||||
// We treat recoverable errors as fatal.
|
||||
_drupal_log_error(isset($types[$error_level]) ? $types[$error_level] : 'Unknown error', $message, $backtrace, $error_level == E_RECOVERABLE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last caller (file name and line of the call, function in which the
|
||||
* call originated) from a backtrace.
|
||||
* Custom PHP exception handler.
|
||||
*
|
||||
* Uncaught exceptions are those not enclosed in a try/catch block. They are
|
||||
* always fatal: the execution of the script will stop as soon as the exception
|
||||
* handler exits.
|
||||
*
|
||||
* @param $exception
|
||||
* The exception object that was thrown.
|
||||
*/
|
||||
function _drupal_exception_handler($exception) {
|
||||
$backtrace = $exception->getTrace();
|
||||
// Add the line throwing the exception to the backtrace.
|
||||
array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
|
||||
|
||||
// For PDOException errors, we try to return the initial caller,
|
||||
// skipping internal functions of the database layer.
|
||||
if ($exception instanceof PDOException) {
|
||||
// The first element in the stack is the call, the second element gives us the caller.
|
||||
// We skip calls that occurred in one of the classes of the database layer
|
||||
// or in one of its global functions.
|
||||
$db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
|
||||
while (($caller = $backtrace[1]) &&
|
||||
((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE)) ||
|
||||
in_array($caller['function'], $db_functions))) {
|
||||
// We remove that call.
|
||||
array_shift($backtrace);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the message to the watchdog and return an error page to the user.
|
||||
_drupal_log_error(get_class($exception), $exception->getMessage(), $backtrace, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a PHP error or exception, display an error page in fatal cases.
|
||||
*
|
||||
* @param $type
|
||||
* The type of the error (Error, Warning, ...).
|
||||
* @param $message
|
||||
* The message associated to the error.
|
||||
* @param $backtrace
|
||||
* The backtrace of function calls that led to this error.
|
||||
* @param $fatal
|
||||
* TRUE if the error is fatal.
|
||||
*/
|
||||
function _drupal_log_error($type, $message, $backtrace, $fatal) {
|
||||
$caller = _drupal_get_last_caller($backtrace);
|
||||
|
||||
// Initialize a maintenance theme early if the boostrap was not complete.
|
||||
// Do it early because drupal_set_message() triggers an init_theme().
|
||||
if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
|
||||
unset($GLOBALS['theme']);
|
||||
define('MAINTENANCE_MODE', 'error');
|
||||
drupal_maintenance_theme();
|
||||
}
|
||||
|
||||
// Force display of error messages in update.php.
|
||||
if (variable_get('error_level', 1) == 1 || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) {
|
||||
drupal_set_message(t('@type: %message in %function (line %line of %file).', array('@type' => $type, '%message' => $message, '%function' => $caller['function'], '%line' => $caller['line'], '%file' => $caller['file'])), 'error');
|
||||
}
|
||||
|
||||
watchdog('php', '%type: %message in %function (line %line of %file).', array('%type' => $type, '%message' => $message, '%function' => $caller['function'], '%file' => $caller['file'], '%line' => $caller['line']), WATCHDOG_ERROR);
|
||||
|
||||
if ($fatal) {
|
||||
drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' Service unavailable');
|
||||
drupal_set_title(t('Error'));
|
||||
if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
|
||||
print theme('page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
|
||||
}
|
||||
else {
|
||||
print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last caller from a backtrace.
|
||||
*
|
||||
* @param $backtrace
|
||||
* A standard PHP backtrace.
|
||||
|
@ -2514,7 +2581,9 @@ function _drupal_bootstrap_full() {
|
|||
require_once DRUPAL_ROOT . '/includes/mail.inc';
|
||||
require_once DRUPAL_ROOT . '/includes/actions.inc';
|
||||
// Set the Drupal custom error handler.
|
||||
set_error_handler('drupal_error_handler');
|
||||
set_error_handler('_drupal_error_handler');
|
||||
set_exception_handler('_drupal_exception_handler');
|
||||
|
||||
// Emit the correct charset HTTP header.
|
||||
drupal_set_header('Content-Type: text/html; charset=utf-8');
|
||||
// Detect string handling method
|
||||
|
|
|
@ -6,13 +6,6 @@
|
|||
* Base classes for the database layer.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A hash value to check when outputting database errors, md5('DB_ERROR').
|
||||
*
|
||||
* @see drupal_error_handler()
|
||||
*/
|
||||
define('DB_ERROR', 'a515ac9c2796ca0e23adbe92c68fc9fc');
|
||||
|
||||
/**
|
||||
* @defgroup database Database abstraction layer
|
||||
* @{
|
||||
|
|
|
@ -322,8 +322,7 @@ class DrupalWebTestCase {
|
|||
* @see set_error_handler
|
||||
*/
|
||||
function errorHandler($severity, $message, $file = NULL, $line = NULL) {
|
||||
$severity = $severity & error_reporting();
|
||||
if ($severity) {
|
||||
if ($severity & error_reporting()) {
|
||||
$error_map = array(
|
||||
E_STRICT => 'Run-time notice',
|
||||
E_WARNING => 'Warning',
|
||||
|
|
|
@ -251,3 +251,52 @@ class DrupalSetContentTestCase extends DrupalWebTestCase {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Drupal error and exception handlers.
|
||||
*/
|
||||
class DrupalErrorHandlerUnitTest extends DrupalWebTestCase {
|
||||
function getInfo() {
|
||||
return array(
|
||||
'name' => t('Drupal error handlers'),
|
||||
'description' => t("Performs tests on the Drupal error and exception handler."),
|
||||
'group' => t('System'),
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('system_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the error handler.
|
||||
*/
|
||||
function testErrorHandler() {
|
||||
$this->drupalGet('system-test/generate-warnings');
|
||||
|
||||
$this->assertErrorMessage('Notice', 'system_test.module', 'system_test_generate_warnings() ', 'Undefined variable');
|
||||
$this->assertErrorMessage('Warning', 'system_test.module', 'system_test_generate_warnings() ', 'Division by zero');
|
||||
$this->assertErrorMessage('User notice', 'system_test.module', 'system_test_generate_warnings() ', 'Drupal is awesome');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the exception handler.
|
||||
*/
|
||||
function testExceptionHandler() {
|
||||
$this->drupalGet('system-test/trigger-exception');
|
||||
$this->assertErrorMessage('Exception', 'system_test.module', 'system_test_trigger_exception()', 'Drupal is awesome');
|
||||
|
||||
$this->drupalGet('system-test/trigger-pdo-exception');
|
||||
$this->assertErrorMessage('PDOException', 'system_test.module', 'system_test_trigger_pdo_exception()', 'Base table or view not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: assert that the logged message is correct.
|
||||
*/
|
||||
function assertErrorMessage($type, $file, $function, $message) {
|
||||
$this->assertText($type, t("Found '%type' in error page.", array('%type' => $type)));
|
||||
$this->assertText($file, t("Found '%file' in error page.", array('%file' => $file)));
|
||||
$this->assertText($function, t("Found '%function' in error page.", array('%function' => $function)));
|
||||
$this->assertText($message, t("Found '%message' in error page.", array('%message' => $message)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,4 +5,3 @@ package = Testing
|
|||
version = VERSION
|
||||
core = 7.x
|
||||
files[] = system_test.module
|
||||
hidden = TRUE
|
||||
|
|
|
@ -48,6 +48,27 @@ function system_test_menu() {
|
|||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
$items['system-test/generate-warnings'] = array(
|
||||
'title' => 'Generate warnings',
|
||||
'page callback' => 'system_test_generate_warnings',
|
||||
'access callback' => TRUE,
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
$items['system-test/trigger-exception'] = array(
|
||||
'title' => 'Trigger an exception',
|
||||
'page callback' => 'system_test_trigger_exception',
|
||||
'access callback' => TRUE,
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
$items['system-test/trigger-pdo-exception'] = array(
|
||||
'title' => 'Trigger a PDO exception',
|
||||
'page callback' => 'system_test_trigger_pdo_exception',
|
||||
'access callback' => TRUE,
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
@ -120,3 +141,30 @@ function system_test_modules_uninstalled($modules) {
|
|||
drupal_set_message(t('hook_modules_uninstalled fired for aggregator'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; generate warnings to test the error handler.
|
||||
*/
|
||||
function system_test_generate_warnings() {
|
||||
// This will generate a notice.
|
||||
$monkey_love = $bananas;
|
||||
// This will generate a warning.
|
||||
$awesomely_big = 1/0;
|
||||
// This will generate a user error.
|
||||
trigger_error("Drupal is awesome", E_USER_NOTICE);
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; trigger an exception to test the exception handler.
|
||||
*/
|
||||
function system_test_trigger_exception() {
|
||||
throw new Exception("Drupal is awesome");
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; trigger an exception to test the exception handler.
|
||||
*/
|
||||
function system_test_trigger_pdo_exception() {
|
||||
db_query("SELECT * FROM bananas_are_awesome");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue