上手并过渡到PHP7(4)——取代fatal error的engine exceptions

上手并过渡到PHP7

取代fatal error的engine exceptions

泊学原文链接
泊学代码秀视频

自从PHP 4以来,PHP的错误处理几乎就是一成不变的。只不过在PHP 5.0里添加了E_STRICT,在PHP 5.2里添加了E_RECOVERABLE_ERROR,在PHP 5.3里,添加了E_DPRECATED这几种Error level。尽管PHP 5中加入了Exception,但PHP中只有很少的模块使用了这个机制(例如:pdo和spl)。在PHP 7中,这个尴尬的现状,终于被彻底改变了。

Engine Exceptions

PHP 7里,几乎所有的Fatal和Catchable fatal error都被替换成了 Engine exceptions 。但是,所有未被catch的异常仍旧会导致一个“传统”的PHP fatal error,因此,对于各种fatal error来说,这个改动几乎是向前兼容的。但对于其他类型的Error(non-fatal)来说,由于它们也被转换成了异常,忽略它们同样会导致一个fatal error,因此,对这些错误的处理,并不向前兼容。

把各种错误统一成异常的一个好处,就是我们可以使用try...catch来统一处理它们,进而,为错误现场的正确清理提供诸多保障:

  • 确保finally内的代码被调用;

  • 确保类的__destruct()函数被调用;

  • 使用register_shutdown_function()注册的回调函数被调用;

总之,因为有了engine exceptions,错误更不容易被忽略,也更容易被处理。我们来看一个例子: 构造函数中发生异常会怎么样呢?

getMessage();
}

在PHP 5里,$msg会是一个null或不可用对象。在PHP 7里,MessageFormatter则会抛出一个\IntlException异常:Constructor failed。

PHP 7 Exception架构

为了能够和PHP 5兼容,我们必须确保之前的call-all写法:

getMessage();
}

不能捕获新的PHP 7 engine exceptions(因为在PHP 7之前,Fatal error是不能够被捕获和处理的)。这样,那些没有被处理的异常,才会像之前一样导致一个Fatal error。因此,所有新的engine exception并没有继承之前的\Exception类,而是继承了一个新的叫做\Error的基类。

class Error implements Throwable {
    /* Inherited methods */
    abstract public string Throwable::getMessage ( void )
    abstract public int Throwable::getCode ( void )
    ...
}

基于\Error exception,派生了5个新的engine exception:ArithmeticError / AssertionError / DivisionByZeroError / ParseError / TypeError。在PHP 7里,无论是老的\Exception还是新的\Error,它们都实现了一个共同的interface: \Throwable。因此,\Throwable是PHP 7异常架构里最顶层的接口。所以,如果你想在PHP 7里实现一个catch-all,你可以这样:

getMessage();
}

Error exception

接下来,我们来分别了解一下新增的这几个engine exception:

\Error

这个异常代表了PHP 7中标准的fatal和catchable-fatal错误,如果它不被catch,就会进而触发一个“传统”的PHP fatal error。例如,我们调用一个不存在的方法:

try {
    nonExistFunc();
}
catch(\Error $e) {
    echo "\Error catch: ".$e->getMessage();
}

\AssertionError

如果你在php.ini里,把assert.exception设置成1,当断言失败的时候,你就会收到这个异常:

try {
    assert('1 > 2', '1 > 2, are your serious?');
}
catch(\AssertionError $e) {
    echo $e->getMessage();
}

“如果我们在assert()里不设置错误信息,\AssertError读不到错误信息的。”
最佳实践

\ArithmeticError and \DivisionByZeroError

\ArithmeticError和算数运算有关。运算发生越界或者bit shift负数位数,都会导致发生\ArithmeticError。例如下面这段代码就会导致“Bit shift by negative number”错误。

try {
    1 >> -1;
}
catch(\ArithmeticError $e) {
    echo $e->getMessage();
}

而\DivisionByZeroError则表示除数为0而导致的错误(无论我们使用 / % 或 intdiv(),只要除数为0,都会导致这个错误)。

\TypeError

我们在前面的视频介绍过PHP 7的scalar type hints以及strict mode。无论是scalar type hints还是传统的type hints(class / interface / callable / array),只要类型不匹配type hints约束的时候,就会导致\TypeErro异常。

try {
    1 >> -1;
}
catch(\ArithmeticError $e) {
    echo $e->getMessage();
}

set_error_handler()

在PHP 7里,有一点是和PHP 5不兼容的,如果我们之前使用set_error_handler()处理catchable fatal error,在PHP 7里,这些error已经变成了engine exception,它们不会再被set_error_handler()处理。

自定义异常

尽管\Throwable是PHP 7中的顶层异常接口,但当我们自定义异常的时候,却不能直接实现它。否则PHP会提示我们下面的错误:

class MyException implements \Throwable {}

Fatal error: Class MyException cannot implement interface Throwable, extend Exception or Error instead

为了能正确处理异常行号、文件名和stack trace,我们只能从\Exception或者\Error派生自己的异常类。但是,我们可以拓展新的\Throwable接口,并且实现其中的方法:

interface MyExceptionInterface extends \Throwable { }

class MyError 
    extends \Error implements MyExceptionInterface { }

相关推荐