PHP及thinkphp中的错误和异常处理

https://blog.csdn.net/weixin_38281213/article/details/119342080
https://www.zhihuclub.com/24055.shtml

对于任何一种语言来说,异常的存在是具备共性的,所以学习一门语言理解异常机制是必不可少的.

什么是错误

当PHP语言遇到异常的情况(比如数据库连接不上或者函数参数传递错误),则会报出一些错误,错误可以分为多种类型,除了E_ERRORE_CORE_ERROR错误,其它错误不会终止程序运行.

PHP让人觉得简单的原因就在于程序不会频繁的报错,给人一种编写流畅和方便的错觉.
也正因为这个原因PHP程序的严谨性和准确性差了不少,比如mysql_fetch_array查询遇到网络错误返回FALSE的时候(程序没有终止运行),假如调用程序认为查询没有匹配的数据,则这个程序本质是错误的.

通过 php.ini的指令 error_reporting或者动态调用 error_reporting()函数我们可以选择报告什么类型的错误,通过 display_errors指令则可以控制错误是否在线输出.而 error_log指令可以控制将错误输出到日志中.

如何正确使用错误

不管是系统函数或者是自定义函数,假如内部遇到错误,如何告之调用者呢?一般是通过函数返回 TRUE或者 FALSE来表明.这种处理方式有几个弊端:

  • 调用者只知道发生了错误,但是返回的错误信息太少,且缺乏错误类型的说明
  • 程序处理逻辑和错误处理混杂在一块,产生的代码会非常的不清晰.

一个小技巧: error_get_last()函数会返回最近错误产生的具体原因.

最佳实践:

  • set_error_handler()函数来托管所有的错误
  • trigger_error()函数可以触发自定义错误,可以用来在函数中代替 return 语句
  • 将所有的错误输出到日志中,同时定义错误类型
  • 对用户显示错误,比如将错误以一种更友好的方式返回给用户
  • 生产环境下 display_errors指令要关闭,开发环境则该指令打开

什么是异常

异常也是一个错误,它具备以下的特点:

  • 异常可以自定义,SPL提供了很多类型的异常,你也可以扩展它
  • 异常最常规的动作就是捕获,这样开发者就能根据具体的错误进行后续处理.比如可以根据异常的上下文给用户返回友好的提示.或者继续抛出一个异常,让上游的程序去处理.假如还是没有捕获异常,那么程序就直接终止了.
  • 异常另外个动作就是抛出,假如通过函数编写业务逻辑,遇到意外的情况,可以直接扔出一个异常.
  • 异常可以被代码一层一层捕获,假如最外层的程序还没有捕获,则代码直接终止运行
  • PHP中的异常假如不能捕获,则作为致命错误写入到系统错误日志中

参考博文地址:

https://www.jianshu.com/p/b641628c6c2d

ERROR的级别
只有熟悉错误级别才能对错误捕捉有更好的认识。 ERROR有不同的错误级别,我之前的一篇文章中有写到:http://www.cnblogs.com/zyf-zhaoyafei/p/3649434.html
下面我再总结性的给出这几类错误级别:

复制代码

 1     Fatal Error:致命错误(脚本终止运行)
 2         E_ERROR         // 致命的运行错误,错误无法恢复,暂停执行脚本
 3         E_CORE_ERROR    // PHP启动时初始化过程中的致命错误
 4         E_COMPILE_ERROR // 编译时致命性错,就像由Zend脚本引擎生成了一个E_ERROR
 5         E_USER_ERROR    // 自定义错误消息。像用PHP函数trigger_error(错误类型设置为:E_USER_ERROR)
 6 
 7     Parse Error:编译时解析错误,语法错误(脚本终止运行)
 8         E_PARSE  //编译时的语法解析错误
 9 
10     Warning Error:警告错误(仅给出提示信息,脚本不终止运行)
11         E_WARNING         // 运行时警告 (非致命错误)。
12         E_CORE_WARNING    // PHP初始化启动过程中发生的警告 (非致命错误) 。
13         E_COMPILE_WARNING // 编译警告
14         E_USER_WARNING    // 用户产生的警告信息
15 
16     Notice Error:通知错误(仅给出通知信息,脚本不终止运行)
17         E_NOTICE      // 运行时通知。表示脚本遇到可能会表现为错误的情况.
18         E_USER_NOTICE // 用户产生的通知信息。

复制代码

由此可知有5类是产生ERROR级别的错误,这种错误直接导致PHP程序退出。
可以定义成:

1 ERROR = E_ERROR | E_CORE_ERROR |  E_COMPILE_ERROR | E_USER_ERROR | E_PARSE

PHP异常处理中的黑科技
前面提到框架中是可以捕获所有的错误和异常的,之所以能实现应该是使用了黑科技,哈哈!其实也不是什么黑科技,主要是三个重要的函数:

1:set_error_handler()
看到这个名字估计就知道什么意思了,这个函数用于捕获错误,设置一个用户自定义的错误处理函数。

复制代码

1 <?php
2     set_error_handler('zyferror');
3     function zyferror($type, $message, $file, $line)
4     {
5       var_dump('<b>set_error_handler: ' . $type . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .</b><br />');
6     }
7 ?>

复制代码

当程序出现错误的时候自动调用此方法,不过需要注意一下两点:第一,如果存在该方法,相应的error_reporting()就不能在使用了。所有的错误都会交给自定义的函数处理。第二,此方法不能处理以下级别的错误:E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,set_error_handler() 函数所在文件中产生的E_STRICT,该函数只能捕获系统产生的一些Warning、Notice级别的错误。
并且他有多种调用的方法:

复制代码

1 <?php
2      // 直接传函数名 NonClassFunction
3      set_error_handler('function_name'); 
4 
5      // 传 class_name && function_name
6      set_error_handler(array('class_name', 'function_name')); 
7 ?>

复制代码

2:register_shutdown_function()
捕获PHP的错误:Fatal Error、Parse Error等,这个方法是PHP脚本执行结束前最后一个调用的函数,比如脚本错误、die()、exit、异常、正常结束都会调用,多么牛逼的一个函数啊!通过这个函数就可以在脚本结束前判断这次执行是否有错误产生,这时就要借助于一个函数:error_get_last();这个函数可以拿到本次执行产生的所有错误。error_get_last();返回的信息:
[type]           – 错误类型
[message] – 错误消息
[file]              – 发生错误所在的文件
[line]             – 发生错误所在的行

复制代码

1 <?php
2     register_shutdown_function('zyfshutdownfunc');
3     function zyfshutdownfunc()
4     {
5         if ($error = error_get_last()) {
6             var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
7         }
8     }
9 ?>

复制代码

通过这种方法就可以巧妙的打印出程序结束前所有的错误信息。但是我在测试的时候我发现并不是所有的错误终止后都会调用这个函数,可以看下面的一个测试文件,内容是:

复制代码

 1 <?php
 2     register_shutdown_function('zyfshutdownfunc');
 3     function zyfshutdownfunc()
 4     {
 5         if ($error = error_get_last()) {
 6             var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
 7         }
 8     }
 9     var_dump(23+-+); //此处语法错误
10 ?>

复制代码

自己可以试一下,你可以看到根本就不会触发zyfshutdownfunc()函数,其实这是一个语法错误,直接报了一个:

1 <?php
2     Parse error: syntax error, unexpected ')' in /www/mytest/exception/try-catch.php on line 71
3 ?>

由此引出一个奇葩的问题:问什么不能触发,为什么框架中是可以的?其实原因很简单,只在parse-time出错时是不会调用本函数的。只有在run-time出错的时候,才会调用本函数,我的理解是语法检查器前没有执行register_shutdown_function()去把需要注册的函数放到调用的堆栈中,所以就根本不会运行。那框架中为什么任何错误都能进入到register_shutdown_function()中呢,其实在框架中一般会有统一的入口index.php,然后每个类库文件都会通过include ** 的方式加载到index.php中,相当与所有的程序都会在index.php中聚集,同样,你写的具有语法错误的文件也会被引入到入口文件中,这样的话,调用框架,执行index.php,index.php本身并没有语法错误,也就不会产生parse-time错误,而是 include 文件出错了,是run-time的时候出错了,所以框架执行完之后就会触发register_shutdown_function();
所以现在可是试一下这个写法,这样就会触发zyfshutdownfunc()回调了:

复制代码

 1 a.php文件
 2 <?php
 3   // 模拟语法错误
 4   var_dump(23+-+);
 5 ?>
 6 
 7 b.php文件
 8 <?php
 9     register_shutdown_function('zyfshutdownfunc');
10     function zyfshutdownfunc()
11     {
12         if ($error = error_get_last()) {
13             var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
14         }
15     }
16     require 'a.php';
17 ?>

复制代码

3:set_exception_handler()
设置默认的异常处理程序,用在没有用try/catch块来捕获的异常,也就是说不管你抛出的异常有没有人捕获,如果没有人捕获就会进入到该方法中,并且在回调函数调用后异常会中止。看一下用法:

复制代码

1 <?php
2     set_exception_handler('zyfexception');
3     function zyfexception($exception)
4     {
5         var_dump("<b>set_exception_handler: Exception: " . $exception->getMessage()  . '</b>');
6     }
7     throw new Exception("zyf exception");
8 ?>

复制代码

四、巧妙的捕获错误和异常
1:把错误以异常的形式抛出(不能完全抛出)
由上面的讲解我们知道,php中的错误是不能以异常的像是捕获的,但是我们需要让他们抛出,已达到扩展 try-catch的影响范围,我们前面讲到过set_error_handler() 方法,他是干嘛用的,他是捕获错误的,所以我们就可以借助他来吧错误捕获,然后再以异常的形式抛出,ok,试试下面的写法:

复制代码

 1 <?php
 2     set_error_handler('zyferror');
 3     function zyferror($type, $message, $file, $line)
 4     {
 5         throw new \Exception($message . 'zyf错误当做异常');
 6     }
 7 
 8     $num = 0;
 9     try {
10         echo 1/$num;
11 
12     } catch (Exception $e){
13         echo $e->getMessage();
14     }
15 ?>

复制代码

好了,试一下,会打印出:

1 Division by zero zyf123

流程:本来是除0错误,然后触发set_error_handler(),在set_error_handler()中相当与杀了个回马枪,再把错误信息以异常的形式抛出来,这样就可以实现错误以异常的形式抛出。大家要注意:这样做是有缺点的,会受到set_error_handler()函数捕获级别的限制。

2:捕获所有的错误
由set_error_handler()可知,他能够捕获一部分错误,不能捕获系统级E_ERROR、E_PARSE等错误,但是这部分可以由register_shutdown_function()捕获。所以两者结合能出现很好的功能。
看下面的程序:

复制代码

 1 a.php内容:
 2 <?
 3     // 模拟Fatal error错误
 4     //test();
 5 
 6     // 模拟用户产生ERROR错误
 7     //trigger_error('zyf-error', E_USER_ERROR);
 8 
 9     // 模拟语法错误
10     var_dump(23+-+);
11 
12     // 模拟Notice错误
13     //echo $f;
14 
15     // 模拟Warning错误
16     //echo '123';
17     //ob_flush();
18     //flush();
19     //header("Content-type:text/html;charset=gb2312");
20 ?>

21 b.php内容:
22 <?
23     error_reporting(0);
24     register_shutdown_function('zyfshutdownfunc');
25     function zyfshutdownfunc()
26     {
27         if ($error = error_get_last()) {
28             var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
29         }
30     }
31 
32     set_error_handler('zyferror');
33     function zyferror($type, $message, $file, $line)
34     {
35         var_dump('<b>set_error_handler: ' . $type . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .</b><br />');
36     }
37 
38     require 'a.php';
39 ?>

复制代码

到此就可以解释开头的那个程序了吧,test.php 如果是单文件执行是不能捕获到错误的,如果你在框架中执行就是可以的,当然你按照我上面介绍的来扩展也是可以的。

五、自定义异常处理和异常嵌套

1:自定义异常处理

在复杂的系统中,我们往往需要自己捕获我们需要特殊处理的异常,这些异常可能是特殊情况下抛出的。所以我们就自己定义一个异常捕获类,该类必须是 exception 类的一个扩展,该类继承了 PHP 的 exception 类的所有属性,并且我们可以添加自定义的函数,使用的时候其实和之前的一样,大致写法如下:

复制代码

 1 <?php
 2     class zyfException extends Exception
 3     {
 4         public function errorzyfMessage()
 5         {
 6             return 'Error line ' . $this->getLine().' in ' . $this->getFile()
 7                 .': <b>' . $this->getMessage() . '</b> Must in (0 - 60)';
 8         }
 9     }
10 
11     $age = 10;
12     try {
13         $age = intval($age);
14         if($age > 60) {
15             throw new zyfException($age);
16         }
17 
18     } catch (zyfException $e) {
19         echo $e->errorzyfMessage();
20 
21     }
22 ?>

复制代码

2:异常嵌套

异常嵌套是比较常见的写法,在自定义的异常处理中,try 块中可以定义多个异常捕获,然后分层传递异常,理解和冒泡差不多,看下面的实现:

复制代码

 1 <?php
 2     $age = 10;
 3     try {
 4         $age = intval($age);
 5         if($age > 60) {
 6             throw new zyfException($age);
 7         }
 8 
 9         if ($age <= 0) {
10             throw new Exception($age . ' must > 0');
11         }
12 
13     } catch (zyfException $e) {
14         echo $e->errorzyfMessage();
15 
16     } catch(Exception $e) {
17         echo $e->getMessage();
18     }
19 ?>

复制代码

当然也可以在catch中再抛出异常给上层:

复制代码

 1 <?php
 2     $age = 100;
 3     try {
 4         try {
 5             $age = intval($age);
 6             if($age > 60) {
 7                 throw new Exception($age);
 8             }
 9 
10         } catch (Exception $e) {
11             throw new zyfException($age);
12 
13         }
14 
15     } catch (zyfException $e) {
16         echo $e->errorzyfMessage();
17     }
18 ?>

复制代码

六、PHP7中的异常处理
现在写PHP必须考虑版本情况,上面的写法在PHP7中大部分都能实现,但是也会有不同点,在PHP7更新中有一条:更多的Error变为可捕获的Exception,现在的PHP7实现了一个全局的throwable接口,原来老的Exception和其中一部分Error实现了这个接口(interface),PHP7中更多的Error变为可捕获的Exception返回给捕捉器,这样其实和前面提到的扩展try-catch影响范围一样,但是如果不捕获则还是按照Error对待,看下面两个:

复制代码

 1 <?php
 2     try {
 3         test();
 4 
 5     } catch(Throwable $e) {
 6         echo $e->getMessage() . ' zyf';
 7     }
 8 
 9     try {
10         test();
11 
12     } catch(Error $e) {
13         echo $e->getMessage() . ' zyf';
14     }
15 ?>

发表评论

电子邮件地址不会被公开。 必填项已用*标注