PHP错误与异常处理
https://www.cnblogs.com/zyf-zhaoyafei/p/6928149.html
请一定要注意,没有特殊说明:本例 PHP Version < 7
说起PHP异常处理,大家首先会想到try-catch,那好,我们先看一段程序吧:有一个test.php文件,有一段简单的PHP程序,内容如下,然后命令行执行:php test.php
1 <?php
2 $num = 0;
3 try {
4 echo 1/$num;
5
6 } catch (Exception $e){
7 echo $e->getMessage();
8 }
9 ?>
我的问题是:这段程序能正确的捕捉到除0的错误信息吗?
如果你回答能,那你就把这篇文章看完吧!应该能学点东西。
本文章分5个部分介绍我的异常处理的理解:
一、异常与错误的概述
PHP中什么是异常:
程序在运行中出现不符合预期的情况,允许发生(你也不想让他出现不正常的情况)但他是一种不正常的情况,按照我们的正常逻辑本不该出的错误,但仍然会出现的错误,属于逻辑和业务流程的错误,而不是编译或者语法上的错误。
PHP中什么是错误:
属于php脚本自身的问题,大部分情况是由错误的语法,服务器环境导致,使得编译器无法通过检查,甚至无法运行的情况。warning、notice都是错误,只是他们的级别不同而已,并且错误是不能被try-catch捕获的。
上面的说法是有前提条件的:
在PHP中,因为在其他语言中就不能这样下结论了,也就是说异常和错误的说法在不同的语言有不同的说法。在PHP中任何自身的错误或者是非正常的代码都会当做错误对待,并不会以异常的形式抛出,但是也有一些情况会当做异常和错误同时抛出(据说是,我没有找到合适的例子)。也就是说,你想在数据库连接失败的时候自动捕获异常是行不通的,因为这就不是异常,是错误。但是在java中就不一样了,他会把很多和预期不一致的行为当做异常来进行捕获。
PHP异常处理很鸡肋?
在上面的分析中我们可以看出,PHP并不能主动的抛出异常,但是你可以手动抛出异常,这就很无语了,如果你知道哪里会出问题,你添加if else解决不就行了吗,为啥还要手动抛出异常,既然能手动抛出就证明这个不是异常,而是意料之中。以我的理解,这就是PHP异常处理鸡肋的地方(不一定对啊)。所以PHP的异常机制不是那么的完美,但是使用过框架的同学都知道有这个情况:你在框架中直接写开头那段php“自动”捕获异常的代码是可以的,这是为什么?看过源码的同学都知道框架中都会涉及三个函数:register_shutdown_function,set_error_handler,set_exception_handler后面我会重点讲解着三个黑科技,通过这几个函数我们可以实现PHP假自动捕获异常和错误。
二、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 ?>
因为PHP7实现了throwable接口,那么就可以使用第一个这种方式来捕获异常。又因为部分Error实现了接口,并且更多的Error变为可捕获的Exception,那么就可以使用第二种方式来捕获异常。下面是在网上找的PHP7的异常层次树:
Throwable
Exception 异常
...
Error 错误
ArithmeticError 算数错误
DivisionByZeroError 除数为0的错误
AssertionError 声明错误
ParseError 解析错误
TypeError 类型错误
就写到这吧,写得手疼,关于错误和异常处理的大致就写这么多,有什么错误请在评论中给出,多谢大家。
PHP错误与异常处理的更多相关文章
- PHP错误以及异常处理
以前一直觉得php的异常处理没有什么,现在才发现这个还真是门学问,于是狠下心来好好研究了一下,写一篇文章,也作备忘吧. 1. php错误 无论是什么语言编程,都会有如下三种错误,当然php也不例外. ...
- Yii中的错误及异常处理
Yii中的错误及异常处理 Yii已经默认已经在CApplication上实现了异常和错误的接管,这是通过php的set_exception_handler, set_error_handler实现的. ...
- 再谈PHP错误与异常处理
博客好久没有更新了,实在惭愧,最近在忙人生大事,哈哈!这段时间没有看什么新的东西,结合项目中遇到的PHP异常处理问题,我又重新梳理了之前模糊的概念,希望对大家理解PHP异常处理有所帮助. 请一定要注意 ...
- Golang错误和异常处理的正确姿势
Golang错误和异常处理的正确姿势 错误和异常是两个不同的概念,非常容易混淆.很多程序员习惯将一切非正常情况都看做错误,而不区分错误和异常,即使程序中可能有异常抛出,也将异常及时捕获并转换成错误.从 ...
- 【PHP】解析PHP中的错误和异常处理
目录结构: contents structure [-] 错误级别 自定义处理器 设置异常日志 自定义异常类 在这篇文章中,笔者将会阐述PHP中的异常处理,希望能够对你有所帮助. 1.错误级别 PHP ...
- ThinkPHP5.0源码学习之注册错误和异常处理机制
在base.php文件中,用一句代码\think\Error::register();实现错误和异常处理机制的注册. // 注册错误和异常处理机制 \think\Error::register(); ...
- 2018/05/02 PHP 之错误与异常处理
在学习中,越学习越觉得自己基础薄弱. 在平常工作中,对于某些错误处理感觉不知道怎么下手,于是决定重新再整理一下. 强烈推荐这篇文章,真的感觉学习到了很多. 部分引用::再谈PHP错误与异常处理 -- ...
- PHP 注册错误和异常处理机制
注册错误和异常处理机制有三个PHP函数需要学习 1. register_shutdown_function('Bootstrap\Library\Frame::fatalError'); 2. set ...
- PHP常用功能块_错误和异常处理 — php(32)
一.错误和异常处理 1.1 错误类型和基本的调试方法PHP程序的错误发生一般归属于下列三个领域: 语法错误:语法错误最常见,并且也容易修复.如:代码中遗漏一个分号.这类错误会阻止脚本的执行. 运行时错 ...
- php 错误和异常处理
一.错误和异常处理 1.1 错误类型和基本的调试方法 PHP程序的错误发生一般归属于下列三个领域: 语法错误: 语法错误最常见,并且也容易修复.如:代码中遗漏一个分号.这类错误会阻止脚本的执行. 运行 ...
随机推荐
- Storm的StreamID使用样例(版本1.0.2)
随手尝试了一下StreamID的的用法.留个笔记. ==数据样例== { "Address": "小桥镇小桥中学对面", "CityCode" ...
- 删除右键菜单中的Git Gui Here、Git Bash Here的方法
修改注册表的方法: 1.点击左下角开始菜单 - 运行(输入regedit)- 确定或者回车: 2.在打开的注册表中找到:HKEY_CLASSES_ROOT,并点HKEY_CLASSES_ROOT前面的 ...
- R Markdown 速查表
- javascript总结27 :特殊引用类型String/Number/Boolean
为了方便操作基本数据类型,JavaScript还提供了三个特殊的引用类型:String/Number/Boolean 1 Number 例如: var s1 = "zhangsan&quo ...
- 使用word写博客
目前大部分的博客作者在写博客这件事情上都会遇到以下3个痛点:1.所有博客平台关闭了文档发布接口,用户无法使用Word,Windows Live Writer等工具来发布博客.2.发布到博客或公众号平台 ...
- C#中使用多线程访问Winform中控件的若干问题
我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题.然而我们并不能用传统方法来做这个问题,下面我将详细的介绍. 首先来看传统方法: public partial ...
- ES 内存深度解析
注: 本文主要针对ES 2.x. “该给ES分配多少内存?” “JVM参数如何优化?“ “为何我的Heap占用这么高?” “为何经常有某个field的数据量超出内存限制的异常?“ “为何感觉上没多少 ...
- model.find(options)
options {Object} 操作选项,会通过 parseOptions 方法解析 return {Promise} 返回单条数据 查询单条数据,返回的数据类型为对象.如果未查询到相关数据,返回值 ...
- MongoDB整理笔记のReplica Sets + Sharding
MongoDB Auto-Sharding 解决了海量存储和动态扩容的问题,但离实际生产环境所需的高可靠.高可用还有些距离,所以有了"Replica Sets + Sharding" ...
- WebApi跨域请求
在实际开发中 会有提供webapi给前端js 直接调用的情况, 这时候就会有存在跨域的情况, 解决方案: 在Global中添加代码 protected void Application_BeginRe ...