- 错误和异常
PHP7中,错误是指 Error 类,异常是指 Exception 类,他们都继承自 Throwable 类。
Exception和Error的不同点:
a. 产生方式不同和性质不同
Exception是异常,只能由用户手动throw new Exception才能产生一个异常;
Error是错误,是程序报错所产生,也可以由用户调用 trigger_error()产生。
由于Exception要由用户自己抛出,因此用户如果要通过Exception捕获错误,就需要在编程时自己判断可能会出现什么错误,并通过if判断加throw new Exception(...)的方式抛出错误,极其不便。
b. 捕获方式不同
Exception要通过 catch(Exception $e) 和 set_exception_handler 捕获;
Error要通过 catch(Error $e) 和 set_error_handler捕获,其中catch只能捕获Fatal Error,set_error_handler只能捕获Waning、Notice 和 Deprecated类型的错误。
在PHP7之后,set_exception_handler 不仅能捕获 Exception,也能捕获 Fatal Error 类型(但是不能捕获notice和warning)。因为PHP7之后,set_exception_handler的回调函数可以传入一个Throwable类的实例,而不仅限于Exception类的实例。
Exception和Error的相同点:
这两个类都继承自Throwable类,用 catch(Throwable $e) 可以同时捕获 Exception 和 Fatal Error类型的错误或异常。
Exception和Error具有相同的方法签名可以获得错误或异常信息:
特别注意:虽然我们自己写的代码很少会 throw Exception,但是类似PDO、Redis之类的PHP类库内部可能会throw new Exception,因此用catch(Exception $e)在这些情况下依旧能产生作用。
平时我们的错误主要包含以下几种(按严重级别排序):
Parse error > Fatal Error > Waning > Notice > Deprecated
以上所有错误级别中,所有错误级别都不属于 Exception。
小结:
catch(Exception $e) 只能捕获Exception,无法捕获Error;
catch(Error $e)只能捕获Fatal Error错误,不能捕获 Warning、Notice、Deprecated错误,也不能捕获Exception。
catch(Throwable $e)可以捕获Fatal Error错误 和 Exception异常。
set_error_handler() 可以捕获Warning、Notice、Deprecated,不能捕获 Fatal Error和Parse error。
set_exception_handler()可以捕获Exception和Error。
被 set_error_handler() 的自定义错误处理函数捕获到错误后,主逻辑仍可继续执行(因为Warning和Notice错误不足以终止程序)。
被 set_exception_handler() 的自定义错误处理函数捕获到错误或异常后,主逻辑不会继续往下执行。
- PHP错误 Error
PHP的错误捕获流程(不含Fatal Error):
a. 如果有注册自定义错误处理函数,则发生Warning、Notice、Deprecated,会被 set_error_handler注册的错误处理函数 myErrorHandler() 捕获。是否报错取决于 myErrorHandler()返回true还是false。
b. 如果没有注册自定义错误处理函数,则直接报错。
set_error_handler()介绍:
第一参 自定义的错误处理函数。
php要求自定义错误处理函数的参数要遵循一下协议规则:
自定义的错误处理函数 return false会直接报错,return true不会报错,return null(或者不return)也不会报错。
第二参 $error_types 表示自定义错误处理函数只捕获哪些级别的错误,不在$error_types范围内的错误不会被$error_handler捕获。
即使在外部设置了error_reporting()不报告 warning/notice 这样的错误,$error_handler也能捕获到warning/notice错误。即 error_reporting()不会干扰到第二参$error_types。
E_STRICT是代码规范性的问题,不算错误。因此E_ALL不包含E_STRICT。
需要注意:如果错误发生在 set_error_handler()调用之前,那么这个错误是无法被捕获的。
下面是 set_error_handler()的官方示例:
注意:error_reporting()会返回handler外界设置的错误级别。例如外界设置了 error_reporting(E_NOTICE),set_error_handler("handler", E_ALL),那么在handler内部调用 error_reporting()会返回 E_NOTICE 而非 E_ALL。
如果在handler之外设置了 error_reporting(E_NOTICE),但是发生了 E_WARNING错误,这个错误依旧能被handler捕获到。如果handler内部要按照 外界的要求只处理 E_NOTICE 的错误,就可以在handler做个if ($errno & error_reporting() == 0){return false;} 的判断,表示handler不处理 E_WARNING,而是直接报错。
- PHP异常 Exception
异常和Fatal Error的捕获流程:
a. 优先被catch捕获,如果没有try catch 则会被 set_exception_handler 捕获。
b. 如果用 set_exception_handler 捕获异常成功,则外界后面的所有代码都不会执行;如果用 try catch捕获成功,则后面的主代码仍可以继续执行。
c. 如果抛出一个Exception而没有try catch 或 set_exception_handler 捕获,就会报一个 Fatal Error错误。
最后,语法错误是无法用 set_error_handler 和 set_exception_handler 捕获的。
使用set_exception_error捕获异常。
现在有一个需求:如何让一个try catch 能够捕获所有的 Exception、Fatal Error、Warning、Notice、Parse错误。
思路:在 set_error_handler的回调函数中,将捕获到的Warning和Notice作为Exception抛出。再用 set_exception_handler 捕获异常。这么一来所有错误都能被当做异常捕获。
无论程序是否把报错,都会在结束时执行 myShutdownFunc(),该函数的作用是做个错误兜底,如果有错误未被myExceptionHandler或者myErrorHandler或者catch捕获而在 myShutdownFunc 中被error_get_last()检测到,它无法阻止该错误报错,myShutdownFunc能做的就是将这个错误记录到日志中。
最后还有一个问题没解决:如何捕获Parse Error语法错误?就好像在Tp5和Laravel这样的框架里面,即使控制器层或者模型层有语法错误也不会引起真正报错,而是将错误记录到日志中。
实际上,php无法捕获语法错误,因为一个文件如果有语法错误,根本不会执行这个脚本里的任何代码就直接报错。
但可以将 错误注册逻辑 和 业务逻辑 分开放到不同文件,通过 include/require 引入业务逻辑的php文件,如果 include("test.php") 的 test.php 有语法错误,则 include就会返回一个Fatal Error错误,Fatal Error错误就会被 myExceptionHandler() 捕获。
如下所示:
如果写成catch(Throwable $e)就会被 catch捕获,会打印123。
目前主流的框架如Tp5和laravel等,它们的错误处理机制就是通过上面的方式实现:
set_error_handler();
set_exception_handler();
register_shutdown_function();
error_get_last();
try catch