PHP7中的异常与错误处理
php 中的 exception, error, throwable
- php 中将代码自身异常(一般是环境或者语法非法所致)称作错误
error
,将运行中出现的逻辑错误称为异常exception
- 错误是没法通过代码处理的,而异常则可以通过
try/catch
来处理 - php7 中出现了
throwable
接口,该接口由error
和exception
实现,用户不能直接实现throwable
接口,而只能通过继承exception
来实现接口
php7 异常处理机制
过去的 php,处理致命错误几乎是不可能的。致命错误不会调用由 set_error_handler()
设置的处理方式,而是简单的停止脚本的执行。
在 php7 中,当致命错误和可捕获的错误(e_error
和 e_recoverable_error
)发生时会抛出异常,而不是直接停止脚本的运行。对于某些情况,比如内存溢出,致命错误则仍然像之前一样直接停止脚本执行。在 php7 中,一个未捕获的异常也会是一个致命错误。这意味着在 php5.x 中致命错误抛出的异常未捕获,在 php7 中也是致命错误。
注意:其他级别的错误如warning
和notice
,和之前一样不会抛出异常,只有fatal
和recoverable
级别的错误会抛出异常。
从 fatal
和 recoverable
级别错误抛出的异常并非继承自 exception
类。这种分离是为了防止现有 php5.x 的用于停止脚本运行的代码也捕获到错误抛出的异常。fatal
和 recoverable
级别的错误抛出的异常是一个全新分离出来的类 error
类的实例。跟其他异常一样,error
类异常也能被捕获和处理,同样允许在 finally
之类的块结构中运行。
throwable
为了统一两个异常分支,exception
和 error
都实现了一个全新的接口:throwable
php7 中新的异常结构如下:
1 interface throwable 2 |- exception implements throwable 3 |- ... 4 |- error implements throwable 5 |- typeerror extends error 6 |- parseerror extends error 7 |- arithmeticerror extends error 8 |- divisionbyzeroerror extends arithmeticerror 9 |- assertionerror extends error
如果在 php7 的代码中定义了 throwable
类,它将会是如下这样:
1 interface throwable{ 2 public function getmessage(): string; 3 public function getcode(): int; 4 public function getfile(): string; 5 public function getline(): int; 6 public function gettrace(): array; 7 public function gettraceasstring(): string; 8 public function getprevious(): throwable; 9 public function __tostring(): string; 10 }
这个接口看起来很熟悉。throwable
规定的方法跟 exception
几乎是一样的。唯一不同的是 throwable::getprevious()
返回的是 throwable
的实例而不是 exception
的。exception
和 error
的构造函数跟之前 exception
一样,可以接受任何 throwable
的实例。
throwable
可以用于 try/catch
块中捕获 exception
和 error
对象(或是任何未来可能的异常类型)。记住捕获更多特定类型的异常并且对之做相应的处理是更好的实践。然而在某种情况下我们想捕获任何类型的异常(比如日志或框架中错误处理)。在 php7 中,要捕获所有的应该使用 throwable
而不是 exception
。
1 try { 2 // code that may throw an exception or error. 3 } catch (throwable $t) { 4 // handle exception 5 }
用户定义的类不能实现 throwable
接口。做出这个决定一定程度上是为了预测性和一致性——只有 exception
和 error
的对象可以被抛出。此外,异常需要携带对象在追溯堆栈中创建位置的信息,而用户定义的对象不会自动的有参数来存储这些信息。
throwable
可以被继承从而创建特定的包接口或者添加额外的方法。一个继承自 throwable
的接口只能被 exception
或 error
的子类来实现。
1 interface mypackagethrowable extends throwable {} 2 3 class mypackageexception extends exception implements mypackagethrowable {} 4 5 throw new mypackageexception();
error
事实上,php5.x 中所有的错误都是 fatal
或 recoverable
级别的错误,在 php7 中都能抛出一个 error
实例。跟其他任何异常一样,error
对象可以使用 try/catch
块来捕获。
1 $var = 1; 2 try { 3 $var->method(); // throws an error object in php 7. 4 } catch (error $e) { 5 // handle error 6 }
通常情况下,之前的致命错误都会抛出一个基本的 error
类实例,但某些错误会抛出一个更具体的 error
子类:typeerror
、parseerror
以及 assertionerror
。
typeerror
当函数参数或返回值不符合声明的类型时,typeerror
的实例会被抛出。
1 function add(int $left, int $right){ 2 return $left + $right; 3 } 4 5 try { 6 $value = add('left', 'right'); 7 } catch (typeerror $e) { 8 echo $e->getmessage(), "\n"; 9 } 10 11 //argument 1 passed to add() must be of the type integer, string given
parseerror
当 include/require
文件或 eval()
代码存在语法错误时,parseerror
会被抛出。
1 try { 2 require 'file-with-parse-error.php'; 3 } catch (parseerror $e) { 4 echo $e->getmessage(), "\n"; 5 }
arithmeticerror
arithmeticerror
在两种情况下会被抛出。一是位移操作负数位。二是调用intdiv()
时分子是 php_int_min
且分母是 -1 (这个使用除法运算符的表达式:php_int_min / -1
,结果是浮点型)。
1 try { 2 $value = 1 << -1; 3 catch (arithmeticerror $e) { 4 echo $e->getmessage();//bit shift by negative number 5 }
devisionbyzeroerror
当 intdiv()
的分母是 0 或者取模操作 (%) 中分母是 0 时,divisionbyzeroerror
会被抛出。注意在除法运算符 (/) 中使用 0 作除数(也即xxx/0这样写)时只会触发一个 warning,这时候若分子非零结果是 inf,若分子是 0 结果是 nan。
1 try { 2 $value = 1 % 0; 3 } catch (divisionbyzeroerror $e) { 4 echo $e->getmessage();//modulo by zero 5 }
assertionerror
当 assert()
的条件不满足时,assertionerror
会被抛出。
ini_set('zend.assertions', 1);
1 ini_set('assert.exception', 1); 2 3 $test = 1; 4 5 assert($test === 0); 6 7 //fatal error: uncaught assertionerror: assert($test === 0)
只有断言启用并且是设置 ini 配置的 zend.assertions = 1
和 assert.exception = 1
时,assert()
才会执行并抛 assertionerror
。
在你的代码中使用 error
用户可以通过继承 error
来创建符合自己层级要求的 error
类。这就形成了一个问题:什么情况下应该抛出 exception
,什么情况下应该抛出 error
。
error
应该用来表示需要程序员关注的代码问题。从 php 引擎抛出的 error
对象属于这些分类,通常都是代码级别的错误,比如传递了错误类型的参数给一个函数或者解析一个文件发生错误。exception
则应该用于在运行时能安全的处理,并且另一个动作能继续执行的情况。
由于 error
对象不应该在运行时被处理,因此捕获 error
对象也应该是不频繁的。一般来说,error
对象仅被捕获用于日志记录、执行必要的清理以及展示错误信息给用户。
编写代码支持 php5.x 和 php7 的异常
为了在同样的代码中捕获任何 php5.x 和 php7 的异常,可以使用多个 catch
,先捕获 throwable
,然后是 exception
。当 php5.x 不再需要支持时,捕获 exception
的 catch
块可以移除。
1 try { 2 // code that may throw an exception or error. 3 } catch (throwable $t) { 4 // executed only in php 7, will not match in php 5.x 5 } catch (exception $e) { 6 // executed only in php 5.x, will not be reached in php 7 7 }
不幸的是,处理异常的函数中的类型声明不容易确定。当 exception
用于函数参数类型声明时,如果函数调用时候能用 error
的实例,这个类型声明就要去掉。当 php5.x 不需要被支持时,类型声明则可以还原为 throwable
。
上一篇: 菠萝咕噜肉是什么地方的菜?制作菠萝咕噜肉有哪些小技巧?
下一篇: 火锅菜单大全窍门你要掌握