作者简介:

       姜海强:闷骚码农,互联网行业摸爬滚打数余载,先后担任中国体育直播TV主程、网信集团先锋支付架构师、奇虎360服务器端资深开发。热爱技术,喜欢分享,热衷领域:PHP/Golang语言、面向对象设计模式、Redis、Yaf、Yii2、微服务等。

视频课程

yaf+yar微服务-腾讯课堂
yaf+yar微服务-51CTO学院
CSDN学院

Github

个人主页
swoole-boot
roach
roach-orm

QQ群:

姜海强的QQ群

公众号:

360tryst公众号

异常与错误处理

PHP的异常与错误是分开的,当程序出现异常时会throw一个\Exception(或子类)对象,但是当出现错误时会触发一个错误。

1. 异常处理

1.1 通过try...catch主动处理异常

  1. <?php
  2. /**
  3. * Class UserNotExistsException
  4. * @datetime 2020/7/2 5:11 下午
  5. * @author roach
  6. * @email jhq0113@163.com
  7. */
  8. class UserNotExistsException extends \Exception
  9. {
  10. }
  11. /**
  12. * Class TransferException
  13. * @datetime 2020/7/2 5:13 下午
  14. * @author roach
  15. * @email jhq0113@163.com
  16. */
  17. class TransferException extends \Exception
  18. {
  19. }
  20. /**
  21. * @param int $inUserId
  22. * @param int $outUserId
  23. * @param float $amout
  24. * @return bool
  25. * @throws TransferException
  26. * @throws UserNotExistsException
  27. * @datetime 2020/7/2 5:15 下午
  28. * @author roach
  29. * @email jhq0113@163.com
  30. */
  31. function transfer($inUserId, $outUserId, $amout)
  32. {
  33. if($inUserId < 1) {
  34. throw new UserNotExistsException('入金用户不存在');
  35. }
  36. if($outUserId < 1) {
  37. throw new UserNotExistsException('出金用户不存在');
  38. }
  39. if(bccomp($amout, '0', 2) <= 0) {
  40. throw new TransferException('转账金额必须大于0');
  41. }
  42. if(bccomp($amout, '100000000', 2) > 0) {
  43. throw new \Exception('服务器内部错误');
  44. }
  45. return true;
  46. }
  47. try {
  48. $result = transfer(1,0, 5);
  49. if($result) {
  50. exit(json_encode([
  51. 'code' => 200,
  52. 'msg' => 'success',
  53. 'data' => []
  54. ]));
  55. }
  56. }catch (\UserNotExistsException $exception) {
  57. exit(json_encode([
  58. 'code' => 404,
  59. 'msg' => $exception->getMessage(),
  60. 'data' => []
  61. ]));
  62. } catch (\TransferException $exception) {
  63. exit(json_encode([
  64. 'code' => 501,
  65. 'msg' => $exception->getMessage(),
  66. 'data' => []
  67. ]));
  68. } catch (\Exception $exception) {
  69. throw $exception;
  70. }

以上例程输出:

  1. {"code":404,"msg":"\u51fa\u91d1\u7528\u6237\u4e0d\u5b58\u5728","data":[]}

当开发人员对异常有明确的预期时,可以使用以上的方式,catch不用类型的异常,然后对不用的异常做各种不同的处理。

1.2 set_exception_handler处理异常

PHP可以通过set_exception_handler方法注册一个回调函数,当没有用try...catch捕获的异常都会交个回调函数处理。

同样调用以上转账逻辑,不同的是,把try...catch代码块注释掉,换成如下代码:

  1. set_exception_handler(function ($exception){
  2. echo $exception->getMessage().PHP_EOL;
  3. });
  4. transfer(1, 1, 0);

以上例程输出:

  1. 转账金额不能小于0

从以上例程我们可以看出,set_exception_handler方式适合做默认统一的异常处理,同时也可以加一些统一的处理(如:异常日志、监控报警等)
try...catch适合做一些开发主动捕获异常,通过主动捕获异常做一些特殊需求处理。

2. 错误处理

PHP语言的error分为如下等级:

常量 说明 备注
E_ERROR 1 致命的运行时错误。这类错误一般是不可恢复的情况,例如内存分配导致的问题。后果是导致脚本终止不再继续运行
E_WARNING 2 运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行
E_PARSE 4 编译时语法解析错误。解析错误仅仅由分析器产生。
E_NOTICE 8 运行时通知。表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。
E_CORE_ERROR 16 在PHP初始化启动过程中发生的致命错误。该错误类似E_ERROR,但是是由PHP引擎核心产生的。
E_CORE_WARNING 32 PHP初始化启动过程中发生的警告(非致命错误) 。类似E_WARNING,但是是由PHP引擎核心产生的。
E_COMPILE_ERROR 64 编译时致命错误。类似E_ERROR, 但是是由Zend脚本引擎产生的。
E_COMPILE_WARNING 128 编译时警告(非致命错误)。类似E_WARNING,但是是由Zend脚本引擎产生的。
E_USER_ERROR 256 用户产生的致命错误信息。类似E_ERROR, 但是是由用户自己在代码中使用PHP函数trigger_error()来产生的。
E_USER_WARNING 512 用户产生的警告(非致命)信息。类似E_WARNING, 但是是由用户自己在代码中使用PHP函数trigger_error()来产生的。
E_USER_NOTICE 1024 用户产生的通知信息。类似E_NOTICE, 但是是由用户自己在代码中使用PHP函数trigger_error()来产生的。
E_STRICT 2048 启用PHP对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
E_RECOVERABLE_ERROR 4096 可被捕捉的致命错误。 它表示发生了一个可能非常危险的错误,但是还没有导致PHP引擎处于不稳定的状态。 如果该错误没有被set_error_handler()捕捉,将成为一个E_ERROR从而脚本会终止运行
E_DEPRECATED 8192 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。
E_USER_DEPRECATED 16384 用户产生的警告信息。 类似E_DEPRECATED, 但是是由用户自己在代码中使用PHP函数trigger_error()来产生的。
E_ALL 30719 E_STRICT之外的所有错误和警告信息。

上面的值(数值或者符号)用于建立一个二进制位掩码,来制定要报告的错误信息。可以使用按位运算符来组合这些值或者屏蔽某些类型的错误。请注意,在 php.ini 之中,只有’|’, ‘~’, ‘!’, ‘^’ 和 ‘&’ 会正确解析。

Error层次结构

  1. Throwable
  2. Error
  3. ArithmeticError
  4. DivisionByZeroError
  5. AssertionError
  6. CompileError
  7. ParseError
  8. TypeError
  9. ArgumentCountError

2.1 try...catch处理错误

PHP7改变了大多数错误的报告方式。不同于传统(PHP5)的错误报告机制,现在大多数错误被作为Error异常抛出。

try...catch捕捉错误代码格式

  1. <?php
  2. try {
  3. //你的代码逻辑
  4. } catch (\Throwable $t) {
  5. //只会在PHP7版本执行,PHP5不会执行
  6. }
  7. catch (\Exception $e)
  8. {
  9. //只会在PHP5执行,PHP7不会执行
  10. }

案例:

  1. <?php
  2. /**
  3. * @param int $inUserId
  4. * @param int $outUserId
  5. * @param float $amout
  6. * @return bool
  7. * @datetime 2020/7/2 5:15 下午
  8. * @author roach
  9. * @email jhq0113@163.com
  10. */
  11. function transfer($inUserId, $outUserId, $amout)
  12. {
  13. if($inUserId < 1) {
  14. trigger_error('入金用户不存在', E_USER_WARNING);
  15. }
  16. if($outUserId < 1) {
  17. trigger_error('出金用户不存在', E_USER_WARNING);
  18. }
  19. if(bccomp($amout, '0', 2) <= 0) {
  20. trigger_error('转账金额必须大于0', E_USER_WARNING);
  21. }
  22. if(bccomp($amout, '100000000', 2) > 0) {
  23. trigger_error('服务器内部错误', E_USER_ERROR);
  24. }
  25. return true;
  26. }
  27. try {
  28. transfer(1, 0, 0);
  29. fff();
  30. }catch (\Throwable $throwable) {
  31. echo $throwable->getMessage().PHP_EOL;
  32. }

以上例程输出:

  1. PHP Warning: 出金用户不存在 in Exception.php on line 18
  2. PHP Warning: 转账金额必须大于0 in Exception.php on line 22
  3. Call to undefined function fff()

从以上例程可以看出,并不是所有的error都能被try...catch捕捉到,当调用不存在的方法fff()时,被try...catch捕捉到了。

2.2 set_error_handler捕捉错误

同样调用以上转账逻辑,增加set_error_handler捕获错误,如下:

  1. <?php
  2. /**
  3. * 致命错误
  4. */
  5. $fatalError = [
  6. E_ERROR => 1,
  7. E_PARSE => 1,
  8. E_CORE_ERROR => 1,
  9. E_CORE_WARNING => 1,
  10. E_COMPILE_ERROR => 1,
  11. E_COMPILE_WARNING => 1
  12. ];
  13. set_error_handler(function() use(&$fatalError){
  14. $args = func_get_args();
  15. $throwable = new \ErrorException($args[1], $args[0], 1, $args[2], $args[3]);
  16. echo $throwable->getMessage().PHP_EOL;
  17. if(isset($fatalError[ $throwable->getCode() ])) {
  18. exit();
  19. }
  20. });
  21. try {
  22. transfer(1, 0, 0);
  23. fff();
  24. }catch (\Throwable $throwable) {
  25. echo $throwable->getMessage().PHP_EOL;
  26. }

以上例程输出:

  1. 出金用户不存在
  2. 转账金额必须大于0
  3. Call to undefined function fff()

关于异常和错误处理就介绍这么多,大家自动动手封装一个面向对象的通用异常处理吧。

QQ群:

姜海强的QQ群

公众号:

360tryst公众号