Modern Error handling in PHP

By Mario Blažek -

PHP has had support for the exception handling for ages; however, comparing it to Java, this support was pretty weak. Initial support for the exception handling was brought into the language from version 5, with two simple built-in exception classes – Exception and ErrorException, with a support for additional classes through SPL. The idea of this blog post is to introduce modern capabilities of PHP exception handling to readers. 

The new interface

While PHP 7 provides both Error and Exception classes, let’s check the Throwable interface first. Both Error and Exception classes implement Throwable interface – it's a basis for any object that can be thrown via throw statement. It only has one exception to notice, it cannot be implemented in userland classes directly, but only through extending the Exception class. Also, it provides a single point for catching both types of error in a single statement:

<?php

try {
// your code
} catch (Throwable $e) {
   echo 'Very nice way to catch Exception and Error exceptions';
}

List of available built-in exception classes since PHP 7.4:

More Exception classes can be found inside PHP’ Standard Library. And the most notable one that comes from the JSON extension is the JsonException class.

Error, what?

In previous versions of PHP, errors were treated quite differently than exceptions. An error was something that was produced in the engine and as long as it was not fatal, it could be handled by a user-defined function.

The problem was that there were several errors that were fatal and that could not be handled by a user-defined error handler. This meant that you couldn’t handle fatal errors in PHP gracefully. There were several side-effects that were problematic, such as the loss of runtime context, destructors would not be called and dealing with them was clunky. In PHP 7, fatal errors are now exceptions and we can handle them very easily. Fatal errors result in an error exception being thrown. You need to handle non-fatal errors with an error-handling function.

Here is an example of catching a fatal error in PHP 7.1. Notice how the non-fatal error is not caught.

<?php 

try {
   // this will generate notice that would not be caught
   echo $someNotSetVariable;
   // fatal error that now actually is caught
   someNoneExistentFunction();
} catch (Error $e) {
      echo "Error caught: " . $e->getMessage();
}

This script will output a notice error for the attempt to access an invalid variable. Trying to call a function that does not exist would result in a fatal error in earlier versions of PHP, but in PHP 7.1 you can catch it. Here is the output for the script:

Notice: Undefined variable: someNotSetVariable on line 3
Error caught: Call to undefined function someNoneExistentFunction()

Error constants

PHP has a lot of constants that are used in relation to errors. These constants are used when configuring PHP to hide or display errors of certain classes.

Here are some of the most commonly seen error codes:

  • E_DEPRECATED – the interpreter will generate this type of warnings if you use a deprecated language feature. The script will definitely continue to run without errors.
  • E_STRICT – similar to E_DEPRECATED, this indicates that you are using a language feature that is not standard currently and might not work in the future. The script will continue to run without any errors thrown.
  • E_PARSE – your syntax could not be parsed so your script won’t start. Script execution will not even start.
  • E_NOTICE – the engine will just print out an informational message. Script execution won’t break and none of the errors will be thrown.
  • E_ERROR – the script cannot continue running and it is being terminated. Will throw errors and how these are going to be handled depends on the error handler.
  • E_RECOVERABLE_ERROR – it indicates that a probably dangerous error occurred, but did not leave the Engine in an unstable state. Further execution depends on the error handler and error will definitely be thrown.

For full list of constants please consult the PHP manual.

Error handler function

The set_error_handler() function is used to tell PHP how to handle standard engine errors that are not instances of the Error exception class. You cannot use an error-handler function for fatal errors. Error exceptions must be handled with try/catch statements. set_error_handler() accepts a callable as its parameter. Callables in PHP can be specified in two ways: either by a string denoting the name of a function or by passing an array that contains an object and the name of a method (in that order). You can specify protected and private methods as the callable in an object. You can also pass null to tell PHP to revert to the usage of the standard error-handling mechanism. If your error handler does not terminate the program and returns, your script will continue executing at the line after the one where the error occurred.

PHP passes parameters to your error-handler function. You can optionally declare these in the function signature if you want to use them in your function.

Here is an example of custom:

<?php

function myCustomErrorHandler(int $errNo, string $errMsg, string $file, int $line) {
echo "Wow my custom error handler got #[$errNo] occurred in [$file] at line [$line]: [$errMsg]";
}

set_error_handler('myCustomErrorHandler');

try {
   what;
} catch (Throwable $e) {
   echo 'And my error is: ' . $e->getMessage();
}

If you run this code in PHP console php -a you should receive similar output:

Error #[2] occurred in [php shell code] at line [3]: [Use of undefined constant what - assumed 'what' (this will throw an Error in a future version of PHP)]

The most prominent PHP libraries that do extensive usage of PHP’s set_error_handler() and can do nice representations of exceptions and error are Whoops or Symony’s Debug and ErrorHandler components. My suggestion is to use one of those. If not, you can always draw inspiration from their code. While Debug component has extensive usage within the Symfony ecosystem, Whoops remains library of choice for Laravel framework. 

For detailed and advanced usage, please consult PHP manual on error handler.

Displaying or suppressing the non-fatal error message

When you application reaches production, you want to hide all system error messages while in production and your code must run without generating warnings or messages. If you are going to show an error message, make sure that it is the one you’ve generated and that it does not include information that could help an attacker break into your system.

In your development environment, you want all errors to be displayed so that you can fix all the issues they relate to, but while in production, you want to suppress any system messages being sent to the user.

To accomplish this, you need to configure PHP using the following settings in your php.ini file:

  • display_errors – can be set to false to suppress messages
  • log_errors – can be used to store error messages in log files
  • error_reporting – can be set to configure which errors trigger a report

The best practice is to handle errors in your application gracefully. In production, you should rather log unhandled errors instead of allowing them to be displayed to the user. The error_log() function can be used to send a message to one of the defined error handling routines. You can also use the error_log() function to send emails, but personally, would rather use a good solution for logging errors and receiving notifications when an error occurs like Sentry or Rollbar.

There is a thing called Error Control Operator – the at-sign (@) – that in the essence can ignore and suppress errors. The usage is very easy – just prepend any PHP expression with at-sign and generated error will be ignored. Although using this operator may look interesting, I urge you not to do that. I like to call it the living relict from the past.

More information for all PHP error related function can be found in manual.

Exceptions

Exceptions are a core part of object-oriented programming and were first introduced in PHP 5.0. An exception is a program state that requires special processing because it’s not running in an expected manner. You can use an exception to change the flow of your program, for example, to stop doing something if certain preconditions are not met.

An exception will bubble up through the call stack if you do not catch it. Let’s check a simple example:

try {
   print "this is our try block n";
   throw new Exception();
} catch (Exception $e) {
   print "something went wrong, caught yah!";
} finally {
   print "this part is always executed n";
}

PHP includes several standard exception types, and the standard PHP library (SPL) includes a few more. Although you don’t have to use these exceptions, doing so means you can use more fine-grained error detection and report. The Exception and Error classes both implement the Throwable interface and, like any other classes, can be extended. This allows you to create flexible error hierarchies and tailor your exception handling. Only a class that implements the Throwable class can be used with the throw keyword. In other words, you can’t declare your own base class and then throw it as an exception.

Robust code can encounter an error and cope with it. Handling exceptions in a sensible way improves the security of your application and makes logging and debugging easier. Managing errors in your application will also allow you to offer your users a better experience. In this section, we cover how to trap and handle errors that occur in your code.

Catching exceptions

Let’s look at this simple example:

<?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
      throw new MyCustomException('There is something wrong.');
}

try {
   throwMyCustomException();
} catch (MyCustomException $e) {
   echo "Your custom exception caught ";
   echo $e->getMessage();
} catch (Exception $e) {
   echo "PHP base exception caught";
}

As you can see, there are two catch clauses. Exceptions will be matched against the clauses from top to bottom until the type of exception matches the catch clause. This very simple throwMyCustomException() function throws MyCustomException and we expect it to be caught in the first block. Any other exceptions that will occur will be caught by the second block. Here we are calling getMessage() method from the base Exception class. You can find more information about additional method in Exception PHP docs.

You can also specify multiple exceptions by separating them with a pipe (|) character.

Let’s look at another example:

<?php

class MyCustomException extends Exception { }
class MyAnotherCustomException extends Exception { }

try {
   throw new MyAnotherCustomException;
} catch (MyCustomException | MyAnotherCustomException $e) {
   echo "Caught : " . get_class($e);
}

This very simple catch block will catch exceptions of type MyCustomException and MyAnotherCustomException.

A bit more advanced catching scenario:

// exceptions.php
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

try {
   throw new NotFoundHttpException();
} catch (\Exception $e) { 
   echo 1;
} catch (NotFoundHttpException $e) { 
   echo 2;
} catch (\Exception $e) { 
   echo 3;
} finally { 
   echo 4;
}

Is this your final answer?

In PHP 5.5 and later, a finally block may also be specified after or instead of catch blocks. Code within the finally block will always be executed after the try and catch blocks, regardless of whether an exception has been thrown, and before normal execution resumes. One common use for the finally block is to close a database connection, but finally can be used wherever you want code to always be executed.

<?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
   throw new MyCustomException('There is something wrong.');
}

try {
   throwMyCustomException();
} catch (MyCustomException $e) {
   echo "Your custom exception caught ";
   echo $e->getMessage();
} catch (Exception $e) {
   echo "PHP base exception caught";
} finally {
  echo "I'm always here";
}

Here is one good example to be in line with how PHP’s catch/finally statements work:

<?php

try {
   try {
      echo 'a-';
      throw new exception();
      echo 'b-';
   } catch (Exception $e) {
      echo 'caught-';
      throw $e;
   } finally {
      echo 'finished-';
   }
} catch (Exception $e) {
   echo 'end-';
}

Exception handler function

Any exception that is not caught results in a fatal error. If you want to respond gracefully to exceptions that are not caught in catch blocks, you’ll need to set a function as the default exception handler.

To do so, you use the set_exception_handler() function, which accepts a callable as its parameter. Your script will terminate after the callable has executed.

The function restore_exception_handler() will revert the exception handler to its previous value.

<?php

class MyCustomException extends Exception { }

function exception_handler($exception) {
   echo "Uncaught exception: " , $exception->getMessage(), "\n";
}

set_exception_handler('exception_handler');

try {
   throw new Exception('Uncaught Exception');
} catch (MyCustomException $e) {
   echo "Your custom exception caught ";
   echo $e->getMessage();
} finally {
   echo "I'm always here";
}

print "Not executed";

Here the simple exception_handler function will be executed after finally block when none of the exception types was matched. The last print statement will never be executed.

For more info consult PHP docs.

The good old “T_PAAMAYIM_NEKUDOTAYIM”

This was probably the most famous PHP error message ever. There has been much controversy about it in recent years. You can read more in an excellent blog post from Phil Sturgeon.  

Today I can proudly say if you run this code with PHP 7 no T_PAAMAYIM_NEKUDOTAYIM will occur:

<?php

class foo
{
   static $bar = 'baz';
}

var_dump('foo'::$bar);

// Output PHP < 7.0:
// PHP Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM) in php shell code on line 1
// Output PHP > 7.0:
// string(3) "baz"
?>

Conclusion

Since the first introduction of exception handling to PHP, many years have passed until we ended up with much more solid and matured exceptional handling that Java had, quite frankly for ages. Error handling in PHP 7 received much attention making it solid good, opening space for future improvements if we actually from today's perspective need one.

Comments

This site uses cookies. Some of these cookies are essential, while others help us improve your experience by providing insights into how the site is being used. For more detailed information on the cookies we use, please check our Privacy Policy.