rfc:throwable-interface

This is an old revision of the document!


PHP RFC: Throwable Interface

Introduction

PHP 7 has introduced exceptions as a replacement for fatal or recoverable fatal errors. These exceptions do not extend Exception, but instead extend a new class BaseException and are named EngineException, TypeException, and ParseException. These classes have a naming scheme that is unintuitive and will lead to confusion, especially for newer users.

Example:

function add(int $left, int $right) {
    return $left + $right;
};
 
try {
    echo add('left', 'right');
} catch (Exception $e) {
    // Handle or log exception.
}

The code above will not catch the TypeException thrown due to the mis-matched type-hint, resulting in the following message to the user:

Fatal error: Uncaught TypeException: Argument 1 passed to add() must be of the type integer, string given

The reason an object named TypeException would not be caught by catch (Exception $e) is not obvious.

Proposal

This RFC proposes a change to the exception hierarchy for PHP 7. This proposal is based on the Throwable pull request created by Sebastian Bergmann and has been fully implemented in the Throwable Interface pull request.

  • interface Throwable
    • Exception implements Throwable
    • Error implements Throwable (Replaces EngineException)
      • TypeError extends Error
      • ParseError extends Error

BaseException will be removed.

Only objects that implement the Throwable interface can be thrown. The proposed patch does not allow userland classes to implement the Throwable interface. Instead all classes declared in userland must extend one of the existing exception classes.

The Throwable interface specifies the following methods:

  • getMessage()
  • getCode()
  • getFile()
  • getLine()
  • getTrace()
  • getTraceAsString()
  • __toString()

While both Exception and Error are implemented using the same code in the interpreter, the Throwable interface does not preclude future classes from implementing the interface differently or from the implementation of Exception and Error to be different in the future.

catch (Error $e) and catch (Throwable $e) may be used to catch respectively Error objects or any Throwable (current or future) object. Users should be discouraged from catching Error objects except for logging or cleanup purposes as Error objects represent coding problems that should be fixed rather than runtime conditions that may be handled.

Error Name Choice

The name Error was chosen to correspond with PHP's other errors. Non-fatal errors detected by PHP will continue to trigger warnings and notices, while fatal errors are thrown as Error exceptions.

Conceptually both of these conditions are error conditions detected by PHP. The only difference is that for some errors the execution of the script can continue from where the error occurred; for others it is not possible for execution to continue from the place where the error occurred, and so instead an exception must be thrown.

Users may wish to use set_error_handler() to throw objects extending Error in their code for non-fatal types of error.

While this name may also cause some confusion for users, other name choices such as Failure do not seem appropriate. It is likely that users would use the term 'Uncaught Error' when searching, minimizing overlap with with non-fatal error messages.

AssertionException

If this RFC is accepted, AssertionException should instead extend Error and be renamed to AssertionError as suggested in the Expectations RFC.

Proposed PHP Version

PHP 7

Backwards Compatibility

Throwable, Error, TypeError, and ParseError will no longer be available in the global namespace.

Patch

A patch for this RFC is available at https://github.com/php/php-src/pull/1284.

References

rfc/throwable-interface.1432317631.txt.gz · Last modified: 2017/09/22 13:28 (external edit)