摘要:主要是参考《PHP核心技术与最佳实践》的第一章 面向对象思想的核心概念的1.6小节
1.1 PHP的异常处理机制
在语言级别上,通常具有许多错误处理模式,但这些处理模式往往简历在约定俗成的基础上,即这些错误是预知的。但是在大型程序中,如果每次调用都需要去逐一检查错误,会使代码变得复杂,到处充斥着if...else,并且严重降低代码的可读性,而且人的因素也是不可依赖的,程序员可能并不会把这些问题当一回事,从而导致业务异常。在这种背景下,逐渐形成了异常处理机制。
PHP中的异常,是程序运行中不符合预期的情况及与正常流程不同的状况。一种不正常的情况,就是按照正常逻辑不该出错但仍然出错的情况,这属于逻辑和业务流程的一种中断,而不是语法错误。PHP中的错误则属于自身问题,是一种非法语法或者是环境问题导致的、编译器无法通过检查甚至是无法运行的情况。
在各种语言中,异常(exception)和错误(error)的概念是不一样的。PHP中,遇到任何自身错误都会触发错误,而不是抛出异常。PHP一旦遇到非正常的代码,通常都是触发错误,而不是抛出异常。在这个意义上,如果想使用异常处理机制来处理不可预料的问题,是做不到的。比如,想在文件不存在的情况下或者是数据库连接打不开的情况下触发异常是不可行的,在PHP中会把它作为错误抛出,而不会作为异常自动捕获。
比如下面的经典的除零问题:
<?php
$a = null;
try {
$a = 5 / 0;
echo $a , PHP_EOL;
} catch (exception $e) {
$e->getMessage();
$a = -1;
}
echo $a;
运行:Warning: Division by zero in C:laragonwwwphp_book1_16_exception.php on line 13
Call Stack:
0.0010 349728 1. {main}() C:laragonwwwphp_book1_16_exception.php:0
可以看到,对于除零这种“异常”情况,PHP认为这是一个错误,直接触发错误,而不会自动抛出异常使程序进入异常流程,即没有进入异常分支,也没有处理异常。PHP只有在你主动throw后,才会捕获异常。也就是说,PHP通常是无法自动 捕获有意义的异常的,它把所有不正常的情况都视为错误,而如果想要捕获这个异常呢,就需要使用if...else结构,保证代码是正常的,然后判断如果除数为0的话,则手动抛出异常,再去捕获。
PHP中的异常机制是不足的,绝大多数情况下无法自动抛出异常,必须用if...else先进行判断,再手动抛出异常。而手动抛异常的意义其实并不大,因为这意味着在代码中已经充分预期到错误的出现,也就算不上真正的‘异常’,同时,这种方式还会使业务的逻辑判断和处理变成纷繁复杂。
注意:Java有一套完整的异常机制,内置很多异常类会自动捕获各种各样的异常,不需要程序员去判断各种异常情况后再手动抛出,编译器会代替程序员进行判断业务是否发生错误,如果发生了则自动抛出异常。所以对于上面的除零问题,Java也可以捕获到而不像PHP那样会抛出错误。
1.2 PHP的异常处理用法
下面使用PHP的异常处理来对表单进行分门别类的处理:
<?php
class emailException extends exception
{
}
class pwdException extends exception
{
public function __toString()
{
// 改写抛出异常结果
return "异常 {$this->getCode()} : {$this->getMessage()} in File : {$this->getFile()} on line : {$this->getLine()}" . PHP_EOL;
}
}
function reg($reg_info = null)
{
if (empty($reg_info) || !isset($reg_info)) {
throw new Exception('参数非法');
}
if (empty($reg_info['email'])) {
throw new emailException('邮件为空');
}
if ($reg_info['pwd'] != $reg_info['repwd']) {
throw new pwdException('两次密码不一致');
}
echo '注册成功';
}
try {
reg(['email' => '8448@qq.com', 'pwd' => 123456, 'repwd' => 12345678]);
} catch (emailException $ee) {
echo $ee->getMessage();
} catch (pwdException $ep) {
echo PHP_EOL , $ep;
echo '特殊处理';
} catch (Exception $e) {
echo $e->getTraceAsString();
echo PHP_EOL , '其他情况,统一处理';
}
运行:
异常 0 : 两次密码不一致 in File : C:laragonwwwphp_book1_18_exception.php on line : 35
特殊处理
在这里,对表单进行异常处理,通过重写异常类、手动抛出异常的方式进行异常处理,这是一种业务异常。
PHP中使用异常处理机制的场景:
- 对程序的悲观预测:比如说这段代码在高并发下可能会产生死锁,那么可以悲观的抛出异常,然后在死锁的时候进行捕获,对异常进行细致的处理
- 程序的需要和对业务的关注:异常是业务处理中必不可少的环节,不能对异常视而不见
- 语言级别的健壮性要求:一旦异常发生后,需要使用try...catch把异常造成的逻辑中断破坏降低到最小范围内,并且经过补救处理后不影响业务逻辑的完整性,乱抛异常和只抛异常而不捕获,或者捕获而不补救,会导致数据混乱
1.3 捕获异常的两种方式
比如有个上传文件的业务需求,要把上传的文件保存到一个目录里,并且在数据库中插入这个文件的记录,那么这两步就是互相关联、密不可分的一个集成的业务,缺一不可。使用异常的代码如下:
<?php
try {
// 可能出错的代码段
if (文件上传不成功) {
throw(上传异常);
}
if (插入数据库不成功) {
throw(数据库操作异常);
}
} catch (异常) {
// 必须的补救措施,如删除文件、删除数据库插入记录
}
// ....
也可以使用这样的方式:
<?php
上传
{
if (文件上传不成功) {
throw(上传异常);
}
if (插入数据库不成功) {
throw(数据库操作异常);
}
// ....
}
try {
上传;
// ....
} catch (上传异常) {
// 必须的补救措施,如删除文件、删除数据库插入记录
} catch (其他异常) {
// 记录日志等
}
上面两种捕获异常的方式,前一种是在异常发生时立刻捕获,后一种是分散抛异常集中捕获。
如果业务很重要,那么异常就需要越早处理约好,以保证程序在意外情况下保持业务处理的一致性。比如说一个操作有多个前提步骤,突然最后一个步骤异常了,那么其他的前提操作都要消除掉才行,以保证数据的一致性,并且在这种核心业务下,有大量的代码来做善后工作,进行数据补救,所以对于这种情况下需要使用第一种捕获异常的方式。
如果异常不是那么重要,并且在单一入口、MVC风格的应用中,为了保持代码流程的统一,则往往使用后一种异常捕获的方式。
1.4 PHP的错误级别
PHP手册中一共定义了16个级别的错误,不过一般用不到这么多,大致可以分为以下几类:
- deprecated:最低级别的错误,表示不推荐、不建议,比如说PHP 5中使用ereg函数会报此类错误(注:PHP 7 已经移除该函数,所以PHP7使用ereg会报致命错误),这种错误一般由于使用不推荐、过时的函数或者是语法造成的,虽不影响正常流程,但还是建议修正
- notice:通知级别的错误,一般是语法存在不当的地方,比如使用未定义的变量,比如数组索引是字符的时候没有加引号等,这种错误不影响正常流程,但是建议修正
- warning:警告级别的错误,在语法中出现很不恰当的情况下会报此错误,比如函数参数不匹配的时候,这种级别的错误会导致得不到预期的结果,需要修改代码
- fetal error:致命错误,会直接导致PHP流程的提前终结,后面的代码不会执行了,这种情况必须修改该错误
- prase error:语法解析错误,最高级别的错误,比如说少了一个分号等,后面的代码也不会执行的。
deprecated错误(只有在PHP7以下版本才会报):
<?php
$date = '2012-12-20';
// 如果是在PHP7.0以下的版本的话会报deprecated错误,不建议使用ereg而是用preg_match来代替
// 如果是在PHP7.0以上版本的话,由于PHP7.0版本移除掉ereg方法了,所以这里会报致命错误,下面的代码也不会执行了
if (ereg("([0-9]{4}) - ([0-9]{1, 2}) - ([0-9]{1, 2})", $date, $regs)) {
echo "$regs[3] . $regs[2] . $regs[1]";
} else {
echo "Invalid date format: $date";
}
运行:
PHP Deprecated: Function ereg() is deprecated in C:laragonwwwphp_book1_20_error.php on line 14
Deprecated: Function ereg() is deprecated in C:laragonwwwphp_book1_20_error.php on line 14
PHP Warning: ereg(): REG_BADBR in C:laragonwwwphp_book1_20_error.php on line 14
notice错误:
<?php
if ($i > 5) {
echo '$i 没有初始化' , PHP_EOL;
}
运行:
Notice: Undefined variable: i in C:laragonwwwphp_book1_20_error.php on line 10
Call Stack:
0.0000 352544 1. {main}() C:laragonwwwphp_book1_20_error.php:0
以及:
<?php
$a = ['o' => 2, 4, 6, 8];
echo $a[o];
运行:
Notice: Use of undefined constant o - assumed 'o' in C:laragonwwwphp_book est.php on line 12
Call Stack:
0.0000 349800 1. {main}() C:laragonwwwphp_book est.php:0
2
warning错误:
<?php
$a = ['o' => 2, 4, 6, 8];
// 由于array_sum只能接收一个参数,所以这里会报Warning
$result = array_sum($a, 3);
运行:
Warning: array_sum() expects exactly 1 parameter, 2 given in C:laragonwwwphp_book est.php on line 10
Call Stack:
0.0000 349416 1. {main}() C:laragonwwwphp_book est.php:0
0.0000 349416 2. array_sum() C:laragonwwwphp_book est.php:10
fetal error错误:
<?php
echo fun();
// 致命错误后下面的代码不会被执行
echo '2222222';
运行:
Fatal error: Uncaught Error: Call to undefined function fun() in C:laragonwwwphp_book est.php on line 9
Error: Call to undefined function fun() in C:laragonwwwphp_book est.php on line 9
Call Stack:
0.0000 349016 1. {main}() C:laragonwwwphp_book est.php:0
prase error错误:
<?php
echo '2222222';
运行:
Parse error: syntax error, unexpected end of file, expecting ',' or ';' in C:laragonwwwphp_book est.php on line 4
PHP中的错误可以使用error_reporting(0)来屏蔽所有的错误,也可以通过在函数前面加@符号来抑制错误信息的输出,比如@mysql_connect()等。
1.5 PHP中的错误处理机制
PHP里有一套错误处理机制,可以使用set_error_handler接管PHP错误处理,也可以使用trigger_error函数主动抛出错误。
set_error_handler() 函数设置用户自定义的错误处理函数,函数用于创建运行期间的用户自己的错误处理方法,它需要先创建一个错误处理函数,然后设置错误级别,语法如下:
set_error_handler(error_function, error_types)
参数描述如下:
error_function:规定发生错误时运行的函数,必需
error_function:规定发生错误时运行的函数,必需
error_types:规定在哪个错误报告级别会显示用户定义的错误,可选,默认为E_ALL
注意:如果使用该函数,会完全绕过标准PHP错误处理函数,如果有必要,用户定义的错误处理程序必须终止(die)脚本。另外,如果使用该函数,代码里的错误抑制符@会失效,这种错误也会被显示出来。
自定义的错误处理函数必须要有4个输入变量:$errno、$errstr、$errfile、$errline
下面是例子:
下面是例子:
<?php
/**
* 自定义的异常处理函数
* 注意:并不能接管致命错误Fatal error
*/
function customError($errno, $errstr, $errfile, $errline)
{
echo " 错误代码: [${errno}] ${errstr}" . PHP_EOL;
echo " 错误所在的代码行: {$errline} 文件 {$errfile}" . PHP_EOL;
echo " PHP版本:" . PHP_VERSION . "(" . PHP_OS . ")" . PHP_EOL;
// 如果觉得有报错的话,需要停止执行的话,可以使用die来终止程序,不加die的话则会继续输出
// die();
}
// 使用自定义的异常处理函数来替代PHP的错误处理
set_error_handler('customError', E_ALL|E_STRICT);
$a = ['o' => 2, 4, 6, 8];
// 由于这里没有加引号,所以这里会报Notice
echo $a[o];
echo PHP_EOL;
// 由于array_sum只能接收一个参数,所以这里会报Warning
$result = array_sum($a, 3);
echo PHP_EOL;
// 由于$i没有定义,所以这里会报Notice
if ($i > 5) {
echo '$i 没有初始化' , PHP_EOL;
}
// 这里会报致命错误Fatal error
echo fun();
运行:
错误代码: [8] Use of undefined constant o - assumed 'o'
错误所在的代码行: 34 文件 C:laragonwwwphp_book1_21_setError.php
PHP版本:7.1.1(WINNT)
2
错误代码: [2] array_sum() expects exactly 1 parameter, 2 given
错误所在的代码行: 38 文件 C:laragonwwwphp_book1_21_setError.php
PHP版本:7.1.1(WINNT)
错误代码: [8] Undefined variable: i
错误所在的代码行: 42 文件 C:laragonwwwphp_book1_21_setError.php
PHP版本:7.1.1(WINNT)
Fatal error: Uncaught Error: Call to undefined function fun() in C:laragonwwwphp_book1_21_setError.php on line 47
Error: Call to undefined function fun() in C:laragonwwwphp_book1_21_setError.php on line 47
Call Stack:
0.0110 353256 1. {main}() C:laragonwwwphp_book1_21_setError.php:0
可以看到,对于notice错误、warning错误都可以接管,但是对于fetal error并不能接管,依然还是会输出该错误。
在PHP异常中,异常处理机制是有限的,无法自动抛出异常,必须手动进行,并且内置异常也是有限,PHP把许多异常看做错误,比如除零问题,被看做是warning错误,这样的话,我们可以使用set_error_handler接管,进而主动抛出异常,把上面的代码优化一下加入异常处理机制即可,代码如下:
<?php
/**
* 使用自定义的异常处理函数处理非致命错误和异常
* 注意:并不能接管致命错误Fatal error
*/
function customError($errno, $errstr, $errfile, $errline)
{
$res = " 错误代码: [${errno}] ${errstr}" . PHP_EOL;
$res .= " 错误所在的代码行: {$errline} 文件 {$errfile}" . PHP_EOL;
$res .= " PHP版本:" . PHP_VERSION . "(" . PHP_OS . ")" . PHP_EOL;
// 自定义错误处理时,手动抛出异常
throw new Exception($res);
}
// 使用自定义的异常处理函数来替代PHP的错误处理
set_error_handler('customError', E_ALL|E_STRICT);
try {
$a = 5 / 0;
// $a = new a();
} catch (Exception $e) {
echo $e->getMessage();
}
运行:
错误代码: [2] Division by zero
错误所在的代码行: 32 文件 C:laragonwwwphp_book1_22_setError.php
PHP版本:7.1.1(WINNT)
这样就可以捕获到异常和非致命的错误,弥补PHP异常处理机制的部分不足,这种曲折迂回的处理方式存在的问题是:必须靠程序员来掌控对异常的处理,对于异常高发区、敏感区,如果程序员处理不好,就会导致业务数据不一致的问题,而优点在于可以获得程序运行时的上下文信息,以进行针对性的补救。
完整的加上错误抑制符的捕获异常和非致命错误的代码为:
<?php
/**
* 使用自定义的异常处理函数会导致错误抑制符@失效
* 注意:并不能接管致命错误Fatal error
*/
/**
* 自定义的异常处理函数
*/
function customError($errno, $errstr, $errfile, $errline)
{
$res = " 错误代码: [${errno}] ${errstr}" . PHP_EOL;
$res .= " 错误所在的代码行: {$errline} 文件 {$errfile}" . PHP_EOL;
$res .= " PHP版本:" . PHP_VERSION . "(" . PHP_OS . ")" . PHP_EOL;
// 自定义错误处理时,手动抛出异常
throw new Exception($res);
}
// 使用自定义的异常处理函数来替代PHP的错误处理
set_error_handler('customError', E_ALL|E_STRICT);
try {
$a = ['o' => 2, 4, 6, 8];
// 由于这里没有加引号,所以这里会报Notice
echo @$a[o];
echo PHP_EOL;
// 由于array_sum只能接收一个参数,所以这里会报Warning
$result = @array_sum($a, 3);
echo PHP_EOL;
// 由于$i没有定义,所以这里会报Notice
if (@$i > 5) {
echo '$i 没有初始化' , PHP_EOL;
}
echo PHP_EOL;
// Warning错误
$a = @(5 / 0);
echo PHP_EOL;
// 这里报致命错误Fatal error
$a = new a();
echo PHP_EOL;
// 这里会报致命错误Fatal error
echo fun();
} catch (Exception $e) {
echo $e->getMessage();
}
运行:
错误代码: [8] Use of undefined constant o - assumed 'o'
错误所在的代码行: 34 文件 C:laragonwwwphp_book1_22_setError_2.php
PHP版本:7.1.1(WINNT)
一旦抛出异常,下面的代码不会运行,并且错误抑制符@会失效
1.6 捕获致命错误
使用上面的方法虽然捕获不到致命错误,但是对于致命错误呢,还是可以通过一些特殊方法对这种错误进行处理的,需要用到一个函数,register_shutdown_function。
这个函数的具体用法可以参考:
代码如下:
<?php
/**
* register_shutdown_function,注册一个会在php中止时执行的函数,中止的情况包括发生致命错误、die之后、exit之后、执行完成之后都会调用register_shutdown_function里面的函数
*/
class Shutdown
{
public function stop()
{
echo 'Begin.' . PHP_EOL;
// 如果有发生错误(所有的错误,包括致命和非致命)的话,获取最后发生的错误
if (error_get_last()) {
print_r(error_get_last());
}
// ToDo:发生致命错误后恢复流程处理
// 中止后面的所有处理
die('Stop.');
}
}
// 当PHP终止的时候(执行完成或者是遇到致命错误中止的时候)会调用new Shutdown的stop方法
register_shutdown_function([new Shutdown(), 'stop']);
// 将因为致命错误而中止
$a = new a();
// 这一句并没有执行,也没有输出
echo '必须终止';
运行:
Fatal error: Uncaught Error: Class 'a' not found in C:laragonwwwphp_book1_23_register_shutdown.php on line 32
Error: Class 'a' not found in C:laragonwwwphp_book1_23_register_shutdown.php on line 32
Call Stack:
0.0000 351920 1. {main}() C:laragonwwwphp_book1_23_register_shutdown.php:0
Begin.
Array
(
[type] => 1
[message] => Uncaught Error: Class 'a' not found in C:laragonwwwphp_book1_23_register_shutdown.php:32
Stack trace:
#0 {main}
thrown
[file] => C:laragonwwwphp_book1_23_register_shutdown.php
[line] => 32
)
Stop.
但是很遗憾的是,错误的代码还是会显示出来,另外,如果把这个致命错误的处理和非致命错误的错误封装在一起,即不论是非致命错误还是致命错误都是会报错,而且报错的格式也是一样的。
更新:PHP7 新增了Throwable和Error异常类,Throwable类是可以捕获到致命错误Error以及异常Exception类,所以可以使用Throwable或者是Error来替代register_shutdown_function函数。(Error类只能捕获错误,不能捕获异常)
代码如下:
<?php
try {
// 将因为致命错误而中止
$a = new a();
// 这一句并没有执行,也没有输出
echo 'end';
} catch (Throwable $e) {
print_r($e);
echo $e->getMessage();
}
运行:
Error Object
(
[message:protected] => Class 'a' not found
[string:Error:private] =>
[code:protected] => 0
[file:protected] => C:laragonwwwphp_book hrowable.php
[line:protected] => 5
[trace:Error:private] => Array
(
)
[previous:Error:private] =>
[xdebug_message] =>
Error: Class 'a' not found in C:laragonwwwphp_book hrowable.php on line 5
Call Stack:
0.0000 349856 1. {main}() C:laragonwwwphp_book hrowable.php:0
)
Class 'a' not found
这样的话,PHP7中使用Throwable来捕获的话比使用register_shutdown_function这个函数来得更方便,也更推荐Throwable。
Todo:屏蔽致命错误
Todo:封装非致命错误和致命错误的处理,使得输出的格式是一模一样的
针对上面的两个需求,自己写了两个方案来解决这个。
第一个方案:
<?php
/**
* 使用自定义的异常处理函数,捕获所有的错误处理,包括非致命的和致命的
* 注意:Throwable是PHP7的新特性,即该程序只能在PHP7及以上版本运行
* 注意:虽然能获取到所有的错误和异常,但是并不能实现统一处理
*/
// 使用自定义的异常处理函数来替代PHP的错误处理
set_error_handler('UnFetalError');
/**
* 自定义错误处理方法,处理非致命错误
* @param $errno int 错误代码
* @param $errstr string 错误信息
* @param $errfile string 错误文件
* @param $errline int 错误行号
* @throws Exception
*/
function UnFetalError($errno, $errstr, $errfile, $errline)
{
$res = ShowError($errno, $errstr, $errfile, $errline);
throw new Exception($res, $errno);
}
/**
* 致命错误处理方法,处理致命错误
* @param Throwable $e
* @return string
*/
function FetalError(Throwable $e)
{
$res = ShowError($e->getCode(), $e->getMessage(), $e->getFile(), $e->getLine());
return $res;
}
/**
* 输出错误信息的格式
* @param $errno int 错误代码
* @param $errstr string 错误信息
* @param $errfile string 错误文件
* @param $errline int 错误行号
* @return string
*/
function ShowError($errno, $errstr, $errfile, $errline)
{
$res = " 错误代码: [{$errno}]" . PHP_EOL;
$res .= " 错误信息: {$errstr}" . PHP_EOL;
$res .= " 错误所在的代码行: {$errline} 行" . PHP_EOL;
$res .= " 文件: {$errfile}" . PHP_EOL;
$res .= " PHP版本:" . PHP_VERSION . " (" . PHP_OS . ")" . PHP_EOL;
return $res;
}
function test()
{
// Warning
// $a = 5 / 0;
// echo PHP_EOL;
// Fatal error
echo fun();
echo PHP_EOL;
echo 'end';
}
try {
test();
// 其他的业务代码(可能会抛异常)
// ...
} catch (Throwable $e) {
// 对错误进行筛选,筛选出致命错误和非致命错误
switch ($e->getCode()) {
case 0:
echo '处理致命错误:' . PHP_EOL;
echo FetalError($e);
break;
default:
echo '处理非致命错误:' . PHP_EOL;
echo $e->getMessage();
break;
}
} catch (Exception $e) {
// 如果PHP版本是低于7的话,那么最好是再加上这个catch来捕获异常(不过不能捕获致命错误)
echo $e->getMessage();
}
运行:
处理致命错误:
错误代码: [0]
错误信息: Call to undefined function fun()
错误所在的代码行: 57 行
文件: C:laragonwwwphp_book1_26_allError.php
PHP版本:7.1.1 (WINNT)
上面的方案虽然能实现需求,但是会把致命错误和非致命错误分开处理。
下面的方案是统一处理致命错误、非致命错误:
<?php
/**
* 采用多重try...catch来捕获所有的异常和错误,并且进行统一处理
* 使用自定义的异常处理函数,捕获所有的错误处理,包括非致命的和致命的
* 注意:Throwable是PHP7的新特性,即该程序只能在PHP7及以上版本运行
*/
// 使用自定义的异常处理函数来替代PHP的错误处理
set_error_handler('CustomError');
/**
* 自定义错误处理方法,处理错误
* @param $errno int 错误代码
* @param $errstr string 错误信息
* @param $errfile string 错误文件
* @param $errline int 错误行号
* @throws Exception
*/
function CustomError($errno, $errstr, $errfile, $errline)
{
$res = ShowError($errno, $errstr, $errfile, $errline);
throw new Exception($res, $errno);
}
/**
* 输出错误信息的格式
* @param $errno int 错误代码
* @param $errstr string 错误信息
* @param $errfile string 错误文件
* @param $errline int 错误行号
* @return string
*/
function ShowError($errno, $errstr, $errfile, $errline)
{
$res = " 错误代码: [{$errno}]" . PHP_EOL;
$res .= " 错误信息: {$errstr}" . PHP_EOL;
$res .= " 错误所在的代码行: {$errline} 行" . PHP_EOL;
$res .= " 文件: {$errfile}" . PHP_EOL;
$res .= " PHP版本:" . PHP_VERSION . " (" . PHP_OS . ")" . PHP_EOL;
return $res;
}
function test()
{
// Warning
// $a = 5 / 0;
// echo PHP_EOL;
// Fatal error
echo fun();
echo PHP_EOL;
// Fatal error
$a = new a();
echo PHP_EOL;
echo 'end';
}
try {
try {
test();
// 其他的业务代码(可能会抛异常)
} catch (Error $e) { // 处理致命错误,转化为异常抛出给上一层
CustomError($e->getCode(), $e->getMessage(), $e->getFile(), $e->getLine());
}
} catch (Throwable $e) {
echo $e->getMessage();
}
采用多个try...catch来捕获,把致命错误转化为异常抛出给上一层,所有的错误都在最上一层的catch(Throwable $e)中进行处理。
上面的两个方案的缺点是只能运行在PHP7版本及以上了,PHP7版本以下并没有Error类和Throwable类。所以PHP7版本以下的话需要通过register_shutdown_function来实现捕获致命错误。
Todo:可以封装成一个异常类