PHP Exception 异常处理和 exit/die

LemonLone
  • 237

一直很难理解异常处理,比如我的程序底层使用了 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 判断文件是否存在,那么:

$file = '/a/b/c.php';
is_file($file) or die('config file not found');
require($file);

或者

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

第一个代码可能简陋了点,不多那个 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 信息吗?

回复
阅读 8.6k
5 个回答
lazyboy
  • 1.5k
✓ 已被采纳

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

上层角色 Upper Role
------------------层分界线------------
当前层角色 Current Role
------------------层分界线------------
下层角色 Lower Role

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

 a. Upper Role作为接受方,Current Role作为行为方
 b. Current Role作为接受方,Lower Role作为行为方

对于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),则有以下选择

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

基于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

aisuhua
  • 1.6k

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

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

try {

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

    echo 'AAA';

} catch (Exception $e) {

    echo 'BBB';

}

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

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

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

BBBCCC

Fatal error: Uncaught exception 'Exception' with message 'This is second exception.' ...

从以上的执行结果,我们可以看出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 自由度会比较大,用异常或许不太方便。

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

vimac
  • 11.5k

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

LemonLone
  • 237
宣传栏