PHP Exception 异常处理和 exit/die

WBOY
Release: 2016-06-06 20:32:57
Original
1785 people have browsed it

一直很难理解异常处理,比如我的程序底层使用了 mysql 数据库连接,而且我的上层所有程序都建立在此基础上(不考虑缓存等其他),比如一个页面要取出当前 url 中 id 指定的 post 内容,当调用底层数据库连接时,结果 mysql_connect 无法连接,那建立在此基础上的应用也再没有执行的必要了,我的 mysql_connect 处不应该直接 exit/die 终止程序吗?即使说要友好的错误提示,那我可以自定义一个函数比如 MyError($code),在此函数中来美化我的输出再在里面决定要不要 exit/die 啊。

如果使用异常处理,那在我觉得所有可能出现不正确的地方,我都必须加上 try/catch 了?那我的一段程序下来岂不是一堆 try/catch ... 这和我直接用 die/exit 或者调用自定义 MyError() 有何分别?如果说 Exception 可以往上传递,但我很多时候就应该当即处理啊,就比如数据库连不上,或者我 include 系统配置的时候文件不存在,这个时候难道不应该当即做出处理吗?

比如我做了单入口,Router -> Controllers -> Services/Models -> ModelBase -> DbFactory -> MySQL -> Driver,是不是我在 Router 外面加个 try/catch 就可以了,里面全都 throw?比如我的 MySQL 中 mysql_connect 出现问题了,我的 Controller 里面在 get 取 id 时,url 中的 id 在数据库中不存在,那这两处我都要 try/catch/throw,然后在 Router 外面的 catch 中再处理吗?

实在是很混乱,求解答,手册都是教怎么用,没教应用场景,还有为什么要用?(那个为什么真心看不出为什么),求指点!能指出具体的应用场景最好了,谢谢~

2015-06-19

很感谢各位的详细回答,谢谢!还有一些问题想麻烦各位,写到评论里不太好,就拿来写到这里了。

比如配置文件丢失这个问题,处理的方法:

1、最原始的直接 require,这样配置文件不存在就直接报错了(报错打开),这样显然不合适,不友好而且会暴露物理路径。

2、我已经预先意识到了 require 可能会出现问题,所以决定在 require 之前先 is_file/file_exists 判断文件是否存在,那么:

<code>$file = '/a/b/c.php';
is_file($file) or die('config file not found');
require($file);
</code>
Copy after login
Copy after login

或者

<code>$file = '/a/b/c.php';
try{
    is_file($file) or throw new Exception('config file not found');
}catch(Exception $e){
    //todo
}
require($file);
</code>
Copy after login
Copy after login

第一个代码可能简陋了点,不多那个 die 可以换成自定义错误提示函数,也可以输出友好的错误信息。

那第二个代码有什么好处呢?是不是我在 //todo 处依旧抛出这个 $e,或者干脆这里不要 try/catch 了?假设我是单入口,最后整个系统的 Exception 都交给最上一层处理?

1、那依照分层处理的概念,现在就假设 config 这段代码为“config层”,首先可以肯定的是我的“Dispatcher层”、“Controller层”、“DB层”、“Model层”统统依赖于它,难道说我要这些层自己各自去处理这个异常吗?还是说这些层都各自再 throw 出去?或者这些层统统不管(我才不管呢,谁最后谁管)?这种“config层”一对多为其它层服务,那“config层”在配置文件丢失的时候不应该自己就处理掉吗?这不也符合异常尽量不扩散吗?

2、由“config层”自己处理的话,那 //todo 处理的时候不也应该 exit/die 吗?否则岂不是又去执行 require 了?

3、那如果它自己 exit/die 掉了,这个时候异常的好处不就仅仅是比我自定义的 MyError 多了 trace 信息吗?

回复内容:

一直很难理解异常处理,比如我的程序底层使用了 mysql 数据库连接,而且我的上层所有程序都建立在此基础上(不考虑缓存等其他),比如一个页面要取出当前 url 中 id 指定的 post 内容,当调用底层数据库连接时,结果 mysql_connect 无法连接,那建立在此基础上的应用也再没有执行的必要了,我的 mysql_connect 处不应该直接 exit/die 终止程序吗?即使说要友好的错误提示,那我可以自定义一个函数比如 MyError($code),在此函数中来美化我的输出再在里面决定要不要 exit/die 啊。

如果使用异常处理,那在我觉得所有可能出现不正确的地方,我都必须加上 try/catch 了?那我的一段程序下来岂不是一堆 try/catch ... 这和我直接用 die/exit 或者调用自定义 MyError() 有何分别?如果说 Exception 可以往上传递,但我很多时候就应该当即处理啊,就比如数据库连不上,或者我 include 系统配置的时候文件不存在,这个时候难道不应该当即做出处理吗?

比如我做了单入口,Router -> Controllers -> Services/Models -> ModelBase -> DbFactory -> MySQL -> Driver,是不是我在 Router 外面加个 try/catch 就可以了,里面全都 throw?比如我的 MySQL 中 mysql_connect 出现问题了,我的 Controller 里面在 get 取 id 时,url 中的 id 在数据库中不存在,那这两处我都要 try/catch/throw,然后在 Router 外面的 catch 中再处理吗?

实在是很混乱,求解答,手册都是教怎么用,没教应用场景,还有为什么要用?(那个为什么真心看不出为什么),求指点!能指出具体的应用场景最好了,谢谢~

2015-06-19

很感谢各位的详细回答,谢谢!还有一些问题想麻烦各位,写到评论里不太好,就拿来写到这里了。

比如配置文件丢失这个问题,处理的方法:

1、最原始的直接 require,这样配置文件不存在就直接报错了(报错打开),这样显然不合适,不友好而且会暴露物理路径。

2、我已经预先意识到了 require 可能会出现问题,所以决定在 require 之前先 is_file/file_exists 判断文件是否存在,那么:

<code>$file = '/a/b/c.php';
is_file($file) or die('config file not found');
require($file);
</code>
Copy after login
Copy after login

或者

<code>$file = '/a/b/c.php';
try{
    is_file($file) or throw new Exception('config file not found');
}catch(Exception $e){
    //todo
}
require($file);
</code>
Copy after login
Copy after login

第一个代码可能简陋了点,不多那个 die 可以换成自定义错误提示函数,也可以输出友好的错误信息。

那第二个代码有什么好处呢?是不是我在 //todo 处依旧抛出这个 $e,或者干脆这里不要 try/catch 了?假设我是单入口,最后整个系统的 Exception 都交给最上一层处理?

1、那依照分层处理的概念,现在就假设 config 这段代码为“config层”,首先可以肯定的是我的“Dispatcher层”、“Controller层”、“DB层”、“Model层”统统依赖于它,难道说我要这些层自己各自去处理这个异常吗?还是说这些层都各自再 throw 出去?或者这些层统统不管(我才不管呢,谁最后谁管)?这种“config层”一对多为其它层服务,那“config层”在配置文件丢失的时候不应该自己就处理掉吗?这不也符合异常尽量不扩散吗?

2、由“config层”自己处理的话,那 //todo 处理的时候不也应该 exit/die 吗?否则岂不是又去执行 require 了?

3、那如果它自己 exit/die 掉了,这个时候异常的好处不就仅仅是比我自定义的 MyError 多了 trace 信息吗?

基于你的这个问题来说,你需要有一定的抽象能力,要有封装的概念,其实我更喜欢称之为分层,也就是我们要建立类似下列概念:

<code>上层角色 Upper Role
------------------层分界线------------
当前层角色 Current Role
------------------层分界线------------
下层角色 Lower Role
</code>
Copy after login

基于上述概念来说,无论exit/die还是Exception可以分为2种情况:

<code> a. Upper Role作为接受方,Current Role作为行为方
 b. Current Role作为接受方,Lower Role作为行为方
</code>
Copy after login

对于a来说,你要考虑的做出的行为的接受方Upper Role是谁?如果Upper Role是Top Role(顶层角色,一般基于我们的应用来说,顶层角色指的是使用浏览器的人),那么就应该用exit/die,这个是要Top Role做出处理的,也就是除了Top Role以外的所有Upper Role无权干涉,而当Upper Role非Top Role,那么就应该用Exception,这样除了Top Role以外的所有Upper Role才有权利去进行一些额外处理。

基于a来说mysql_connect连接的事情,(按照我的设计)在使用mysql_connect时,Current Role指的是DB处理连接mysql相关逻辑,而Upper Role是调用DB的未知逻辑(想想这里我为什么要用“未知”这个概念),Current Role仅仅知道mysql_connect连接失败了,而并不知道Upper Role是否还有其他DB或额外选择,那么Current Role为了让Upper Role有一定选择权,这里(我)选择使用Exception,让Upper Role决定是交给Top Role处理还是更上层的Upper Role处理。

对于b的情况来说,Current Role要判断Lower Role是不是会有一些非Current Role期望的情况出现(即:Exception),则有以下选择

<code>是否有非Current Role期望的情况
如果没有,Current Role不需要try/catch,
如果有,Current Role需要处理么?
    Current Role需要处理,try/catch
       Current Role可以处理?
           可以处理,处理
           Upper Role需要处理Current Role的非期望,throw          
    Current Role不需要处理,无视         
</code>
Copy after login

基于b来说mysql_connect连接的事情,Current Role指的是需要用到DB的相关逻辑,而Lower Role是DB处理连接mysql相关逻辑,在Current Role使用的时候,Current Role期望mysql可以正常连接,但是额外情形也会出现,那么Current Role就要考虑自身的情况来选择,比如说Current Role是一个select操作,那么Current Role就是可以返回为空,那么对于msyql连接失败或者数据表真的为空,性质一样,Current Role就可以将非期望舍弃掉(捕获不处理)返回为空,而对于一个update来说,可以选择处理或者不处理都可以,而对于一个可以选择n个mysql 连接的逻辑来说,就必须处理,等等

总之,啰嗦了一堆,就是说你要用好异常,那么你要有上下层的概念,并且在上下层逻辑处理中,要分清Current Role是可以终止程序执行(exit)还是交由Upper Role处理(throw Excepton)

最后,throw是Current Role反馈给Upper Role,try/catch是Current Role处理Lower Role反馈,希望你能更好的使用Exception

楼主应该没有理解好 异常的执行机制,先读读PHP官方手册对异常的描述 点这里。

先不说异常该怎样使用,我们先看看 Exception 执行过程

<code>try {

    throw new Exception('This is first exception.');

    echo 'AAA';

} catch (Exception $e) {

    echo 'BBB';

}

//这里会输出
echo 'CCC';

throw new Exception('This is second exception.');

</code>
Copy after login

你觉得上面的输出结果是什么呢? 下面是答案

<code>BBBCCC

Fatal error: Uncaught exception 'Exception' with message 'This is second exception.' ...
</code>
Copy after login

从以上的执行结果,我们可以看出Exception的一些特点:

  1. try 与 catch 一定是成对出现的,try{ } 中包含可能会抛出异常的代码。
  2. try { } 中的代码一旦发生异常,那么异常后面的代码将停止执行。(这里特指try{ }里面的代码)
  3. 使用 catch(){ } 捕捉并处理完异常后,try{ }和catch(){} 后面的代码(比如这里的 echo 'CCC')会继续执行
  4. 如果异常没有被捕捉,那么将抛出一个致命错误 Fatal error。

结合以上的特点,那么贴主提出来的疑问,就比较容易解决了。


问题1:是不是每个有异常的地方都要try catch 一下?

我的答案:这样做太麻烦了,而且一般没必要,你可以在所有可能抛出异常的地方,用一个 try catch 包含即可。catch 到异常后,对谁处理,怎么样处理都可以。这个也满足单一入口,单一出口的开发理念


问题2:遇到错误,是抛 Exception 还是使用 die()/exit()

我的答案:异常你可以随便抛,但最终你必须有个地方处理。处理完后(比如记录日志等等),最终你要把错误告诉前端用户,告诉这个过程就是输出。一旦发生错误时,我推荐使用异常 Exception,因为它符合单一出口开发理念,这样你可以监控所有页面输出。 如果到处使用 die()/exit()来直接输出,那就无法控制输出了

再者,Exception 实际上本来就包含更丰富的信息,比如行数line、错误代码code、错误堆栈信息trace等等。所以不是更好吗?


问题3:应用场景问题,该什么时候用?

我的答案:异常在任何情况下用都可以,关键你想Exception起到什么作用了。比如自己在写API服务程序时,任何错误(包括程序异常、业务逻辑错误、字段类型错误等等),一律抛异常,一律由一个地方处理。实际上,我感觉这种开发体验是非常棒的,因为第一次写的时候,我不是使用Exception的,囧~。但这里有一个疑问:API 它是没有界面的,它只负责返回一些数据,而且返回的数据格式要求一般是统一的规范的

如果你在写一个前端程序,我一边写一边想,可不可以这样使用 Exception任何程序错误抛异常,任何业务上的验证出错时使用比如 return/echo/die 返回这样子。 因为毕竟异常能表达的东西自然是有限的,如果有些错误(一般业务错误)返回非常复杂,而且前端应用非API 自由度会比较大,用异常或许不太方便。

上面就是自己的愚见吧,有不对的地方,希望指出,共同进步吧!下班了,走人。。。

如果你希望全局捕获错误,或者异常,请看
http://cn2.php.net/manual/zh/function.set-error-handler.php
http://cn2.php.net/manual/zh/function.set-exception-handler.php

都允许用一个callable对象捕获错误或者异常

PHP调用exit退出脚本执行不会导致PHP服务退出,这个跟其他语言有本质区别,所以其他语言只好用try/catch捕获异常或判断返回值来进一步处理,对PHP则不是必须的。

看了这个帖子:http://bbs.phpchina.com/thread-212378-1-1.html,我的问题里可能没有理解异常,像数据库连不上或者配置文件丢失这种或许 halt 更安全直接虽然有点粗暴。

Related labels:
php
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template