Using catch from Throwable instead of Exception

When I write PHP-CLI applications or when I use PHP for scripting only, I often find myself in the situation to create a safe block.

The block is basically a try-catch block that encapsulates the whole code.
In PHP-CLI applications it oftens allows for retrying of certain things via fgets(STDIN).
In scripting applications it is mostly an error mail sending and ROLLBACK command for the database.

If the project I am working on is non framework-based but spans multiple files, I often register error handlers to get alerted when anything doesn’t work as expected. My classic code looks about like this:


<?php

function bindCustomErrorHandler() {

// We should use our custom function to handle errors.
error_reporting(-1);
ini_set('display_startup_errors', 1);

set_error_handler('nachsenden_error_handler');
}

function unbindCustomErrorHandler() {

error_reporting(0);
ini_set('display_startup_errors', 0);
restore_error_handler();

}

function generateCallTrace()
{
$e = new Exception();
$trace = explode("\n", $e->getTraceAsString());
// reverse array to make steps line up chronologically

array_shift($trace); // remove call to this method
array_pop($trace); // remove {main}

return "\t" . implode("\n\t", $trace);
}

function sendErrorMail($message, $attach_dump = true, $additional_receipients = array()){

# since this is the last bastion in error handling, we may NOT produce an error here
unbindCustomErrorHandler();

// code to mail the error

# rebuild the error handler
bindCustomErrorHandler();

return $mailer_return;

}

// Our custom error handler
function custom_error_handler($number, $message, $file, $line, $vars)
{

$email = "
An error ($number) occurred on line $line and in the file: $file.
----
$message
----
Backtrace:
". generateCallTrace(). "
------
Vars: ". print_r($vars, true). PHP_EOL ;

sendErrorMail($email, true, array(__MAIL__ADDRESS__));

if ( ($number !== E_NOTICE) && ($number < 2048) ) {
if(!headers_sent() && defined('ERROR_TARGET')) {
header('Location: '. ERROR_TARGET);
die();
} elseif(defined('ERROR_TARGET')) {
die('Error! Please try again later. Will be redirecting: <meta http-equiv="refresh" content="0; URL='.ERROR_TARGET.'">');
} else {
sendErrorMail("Warning: Error target not defined for error and header already sent!");
die("Error! Please try again later.");
}
}
return true;
}

function exception_handler($e) {

sendErrorMail("Uncaught Exception on line {$e->getLine()} of file {$e->getFile()}: {$e->getMessage()}\n\nTrace: {$e->getTraceAsString()}\n\n", true, array(__MAIL__ADDRESS__));

if(!headers_sent() && defined('ERROR_TARGET')) {
header('Location: '. ERROR_TARGET);
die();
} elseif(defined('ERROR_TARGET')) {
die('Error! Please try again later. Will be redirecting: <meta http-equiv="refresh" content="0; URL='.ERROR_TARGET.'">');
} else {
sendErrorMail("Warning: Error target not defined for exception and header already sent!");
die("Error! Please try again later.");
}
}
set_exception_handler('exception_handler');

function shutDownFunction() {
$error = error_get_last();
if(!is_null($error)) {

sendErrorMail('Shutdown Error - will try to redirect after this: '.var_export($error, true), true, array(__MAIL__ADDRESS__));

if(!headers_sent() && defined('ERROR_TARGET')) {
header('Location: '. ERROR_TARGET);
die();
} elseif(defined('ERROR_TARGET')) {
die('Error! Please try again later. Will be redirecting: <meta http-equiv="refresh" content="0; URL='.ERROR_TARGET.'">');
} else {
sendErrorMail("Warning: Error target not defined for shutdown_handler and header already sent!");
die("Error! Please try again later.");
}
}
}

register_shutdown_function('shutdownFunction');

bindCustomErrorHandler();

date_default_timezone_set('Europe/Berlin');
libxml_use_internal_errors(true);

It is very little commented, but I think it is self-explanatory. Basically for all errors that can be detected from inside PHP I register a handler that tells me, that an error occured.
If possible I attach dumps and tracebacks. For E_NOTICE I continue processing.

One important part I learned is to unregister the error_handler when sending the mail, but actually rather suppress all errors. When using 3rd-Party libraries here an unexpected E_NOTICE may come in unhandy 😉

What this code can’t rescue is errors that lead to HTTP 500 errors. Therefore: Always watch your logs!

The new way in PHP 7

It is always better however to catch errors where they occur. Here you can attach better dumps and be more in context.

In PHP 7 this is now also possible for runtime errors like accessing an element in an unexpected way (json_decode[‘x’] I am looking at you!) or having syntax errors in included files.

So: Other than in Ruby, where it is generally not advised to rescue from the most basic Exception class (as you can easily interfere with process management), this is not true for PHP.
You can also catch SIGKILL or other signals, but this is not done through try-catch.

Therefore, catching Throwable instead of Exception should always be considered a good option.