天天看点

在PHP7中抛出异常和错误

来源:https://trowski.com/2015/06/24/throwable-exceptions-and-errors-in-php7/

Handling fatal errors in the past has been next to impossible in PHP. A fatal error would not invoke the error handler set by 

set_error_handler()

 and would simply halt script execution.

In PHP 7, an exception will be thrown when a fatal and recoverable error (

E_ERROR

 and 

E_RECOVERABLE_ERROR

) occurs, rather than halting script execution. Fatal errors still exist for certain conditions, such as running out of memory, and still behave as before by immediately halting script execution. An uncaught exception will also continue to be a fatal error in PHP 7. This means if an exception thrown from an error that was fatal in PHP 5.x goes uncaught, it will still be a fatal error in PHP 7.

Note that other types of errors such as warnings and notices remain unchanged in PHP 7. Only fatal and recoverable errors throw exceptions.

Exceptions thrown from fatal and recoverable errors do not extend 

Exception

. This separation was made to prevent existing PHP 5.x code from catching exceptions thrown from errors that used to halt script execution. Exceptions thrown from fatal and recoverable errors are instances of a new and separate exception class: 

Error

. Like any other exception, 

Error

 may be caught and handled and will allow any 

finally

 blocks to be executed.

Prior to PHP 7 alpha-2, the exception hierarchy in PHP 7 was different. Fatal and recoverable errors threw instances of 

EngineException

, which did not inherit from 

Exception

. Both 

Exception

 and 

EngineException

 inherited from 

BaseException

. The hierarchy was revised with the RFC I authored,  Throwable Interface. I felt switching to 

Throwable

 and 

Error

 was important to avoid confusion from classes using the suffix Exception that did not extend 

Exception

, as well as being more concise and appealing names.

Throwable

To unite the two exception branches, 

Exception

 and 

Error

 both implement a new interface, 

Throwable

.

The new exception hierarchy in PHP 7 is as follows:

interface Throwable
    |- Exception implements Throwable
        |- ...
    |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- ArithmeticError extends Error
            |- DivisionByZeroError extends ArithmeticError
        |- AssertionError extends Error
           

PHP 7 exception hierarchy

If 

Throwable

 was defined in PHP 7 code, it would look like the code below.

interface Throwable
{
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getTraceAsString(): string;
    public function getPrevious(): Throwable;
    public function __toString(): string;
}
           

Throwable interface

This interface should look familiar. 

Throwable

 specifies methods nearly identical to those of 

Exception

. The only difference is that 

Throwable::getPrevious()

 can return any instance of 

Throwable

 instead of just an 

Exception

. The constructors of 

Exception

 and 

Error

 accept any instance of 

Throwable

 as the previous exception.

Throwable

 may be used in 

try/catch

 blocks to catch both 

Exception

 and 

Error

 objects (or any possible future exception types). Remember that it is better practice to catch more specific exception classes and handle each accordingly. However, some situations warrant catching any exception (such as for logging or framework error handling). In PHP 7, these catch-all blocks should catch 

Throwable

 instead of 

Exception

.

try {
    // Code that may throw an Exception or Error.
} catch (Throwable $t) {
    // Handle exception
}
           

Throwable may be used for catch-all try/catch blocks in PHP 7

User-defined classes cannot implement 

Throwable

. This choice was made in part for predictability and consistency: only instances of 

Exception

 or 

Error

 may be thrown. Additionally, exceptions carry information about where the object was created in the stack trace. A user-defined object does not automatically have parameters to store this information.

Throwable

 can be extended to create package-specific interfaces or add additional methods. An interface extending 

Throwable

 can only be implemented by a class extending either 

Exception

 or 

Error

.

interface MyPackageThrowable extends Throwable {}

class MyPackageException extends Exception implements MyPackageThrowable {}

throw new MyPackageException();
           

Extending Throwable to make a package-specific exception interface

Error

Virtually all errors in PHP 5.x that were fatal errors or recoverable fatal errors now throw instances of 

Error

 in PHP 7. Like any other exception, 

Error

 objects can be caught using a 

try/catch

 block.

$var = 1;

try {
    $var->method(); // Throws an Error object in PHP 7.
} catch (Error $e) {
    // Handle error
}
           

Catching Error objects

Usually an instance of the base 

Error

 class is thrown from previously fatal errors, but some errors will throw a more specific subclass of 

Error

TypeError

ParseError

, and 

AssertionError

.

TypeError

TypeError

 instance is thrown when a function argument or return value does not match a type declaration.

function add(int $left, int $right)
{
    return $left + $right;
}

try {
    $value = add('left', 'right');
} catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}
           

TypeErrors are thrown from mis-matched parameter or return types.

Argument 1 passed to add() must be of the type integer, string given
           

Output of the example above

ParseError

ParseError

 is thrown when an included/required file or 

eval()

'd code contains a syntax error.

try {
    require 'file-with-parse-error.php';
} catch (ParseError $e) {
    echo $e->getMessage(), "\n";
}
           

ParseErrors are thrown from include/require statements or eval()'d code with parse errors

ArithmeticError

An 

ArithmeticError

 is thrown in two situations: bit shifting by a negative number or calling 

intdiv()

with a numerator of 

PHP_INT_MIN

 and denominator of 

-1

 (the expression using the division (

/

) operator, 

PHP_INT_MIN / -1

, will return a float).

try {
    $value = 1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage(), "\n";
}
           

ArithmeticError is thrown from negative bit shifts.

DivisionByZeroError

DivisionByZeroError

 is thrown from 

intdiv()

 when the denominator is zero or when zero is used as the denominator with the modulo (

%

) operator. Note that using zero with the division (

/

) operator only issues a warning and evaluates to 

NaN

 if the numerator is zero or 

Inf

 for any non-zero numerator.

try {
    $value = 1 % 0;
} catch (DivisionByZeroError $e) {
    echo $e->getMessage(), "\n";
}
           

Modulo will throw a DivisionByZeroError if the denominator is zero.

AssertionError

When the condition set by 

assert()

 is not met, an 

AssertionError

 will be thrown.

ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

$test = 1;

assert($test === 0);
           

An AssertionError is thrown if the condition in assert() is not met and assert.exception = 1

Fatal error: Uncaught AssertionError: assert($test === 0)
           

Output of the example above

assert()

 is only executed and will only throw an 

AssertionError

 if assertions are enabled and set to throw exceptions with ini settings 

zend.assertions = 1

 and 

assert.exception = 1

.

Using Error in Your Code

Users are able to create 

Error

 as well as extend 

Error

 to create your own hierarchy of 

Error

 classes. This poses an important question: what situations should throw an instance of a class extending 

Exception

 and what situations should throw an instance of a class extending 

Error

?

Error

 should be used to represent coding issues that require the attention of a programmer. 

Error

objects thrown from the PHP engine fall into this category, as they generally result from coding errors such as providing a parameter of the wrong type to a function or a parse error in a file. 

Exception

 should be used for conditions that can be safely handled at runtime where another action can be taken and execution can continue.

Since 

Error

 objects should not be handled at runtime, catching 

Error

 objects should be uncommon. In general, 

Error

 objects should only be caught for logging, performing any necessary cleanup, and display an error message to the user.

Writing Code to Support PHP 5.x and 7 Exceptions

To catch any exception in PHP 5.x and 7 with the same code, multiple catch blocks can be used, catching 

Throwable

 first, then 

Exception

. Once PHP 5.x support is no longer needed, the block catching 

Exception

 can be removed.

try {
    // Code that may throw an Exception or Error.
} catch (Throwable $t) {
    // Executed only in PHP 7, will not match in PHP 5.x
} catch (Exception $e) {
    // Executed only in PHP 5.x, will not be reached in PHP 7
}
           

Writing catch-all block compatible with both PHP 5.x and 7

Unfortunately, type declarations on functions that handle exceptions are not as easy to fix. If 

Exception

 is used as a type declaration on a function parameter, the type declaration will need to be removed if the function could be called with an instance of 

Error

. When PHP 5.x support is not required, the type declaration can be restored as 

Throwable

.