目录
2015-06-19
回复内容:
首页 后端开发 php教程 PHP Exception 异常处理和 exit/die

PHP Exception 异常处理和 exit/die

Jun 06, 2016 pm 08:32 PM
php

一直很难理解异常处理,比如我的程序底层使用了 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>
登录后复制
登录后复制

或者

<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>
登录后复制
登录后复制

第一个代码可能简陋了点,不多那个 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>
登录后复制
登录后复制

或者

<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>
登录后复制
登录后复制

第一个代码可能简陋了点,不多那个 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>
登录后复制

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

<code> a. Upper Role作为接受方,Current Role作为行为方
 b. Current Role作为接受方,Lower Role作为行为方
</code>
登录后复制

对于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>
登录后复制

基于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>
登录后复制

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

<code>BBBCCC

Fatal error: Uncaught exception 'Exception' with message 'This is second exception.' ...
</code>
登录后复制

从以上的执行结果,我们可以看出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 更安全直接虽然有点粗暴。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 带来了多项新功能、安全性改进和性能改进,同时弃用和删除了大量功能。 本指南介绍了如何在 Ubuntu、Debian 或其衍生版本上安装 PHP 8.4 或升级到 PHP 8.4

我后悔之前不知道的 7 个 PHP 函数 我后悔之前不知道的 7 个 PHP 函数 Nov 13, 2024 am 09:42 AM

如果您是一位经验丰富的 PHP 开发人员,您可能会感觉您已经在那里并且已经完成了。您已经开发了大量的应用程序,调试了数百万行代码,并调整了一堆脚本来实现操作

如何设置 Visual Studio Code (VS Code) 进行 PHP 开发 如何设置 Visual Studio Code (VS Code) 进行 PHP 开发 Dec 20, 2024 am 11:31 AM

Visual Studio Code,也称为 VS Code,是一个免费的源代码编辑器 - 或集成开发环境 (IDE) - 可用于所有主要操作系统。 VS Code 拥有针对多种编程语言的大量扩展,可以轻松编写

在PHP API中说明JSON Web令牌(JWT)及其用例。 在PHP API中说明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一种基于JSON的开放标准,用于在各方之间安全地传输信息,主要用于身份验证和信息交换。1.JWT由Header、Payload和Signature三部分组成。2.JWT的工作原理包括生成JWT、验证JWT和解析Payload三个步骤。3.在PHP中使用JWT进行身份验证时,可以生成和验证JWT,并在高级用法中包含用户角色和权限信息。4.常见错误包括签名验证失败、令牌过期和Payload过大,调试技巧包括使用调试工具和日志记录。5.性能优化和最佳实践包括使用合适的签名算法、合理设置有效期、

php程序在字符串中计数元音 php程序在字符串中计数元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符组成的序列,包括字母、数字和符号。本教程将学习如何使用不同的方法在PHP中计算给定字符串中元音的数量。英语中的元音是a、e、i、o、u,它们可以是大写或小写。 什么是元音? 元音是代表特定语音的字母字符。英语中共有五个元音,包括大写和小写: a, e, i, o, u 示例 1 输入:字符串 = "Tutorialspoint" 输出:6 解释 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。总共有 6 个元

您如何在PHP中解析和处理HTML/XML? 您如何在PHP中解析和处理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示了如何使用PHP有效地处理XML文档。 XML(可扩展的标记语言)是一种用于人类可读性和机器解析的多功能文本标记语言。它通常用于数据存储

解释PHP中的晚期静态绑定(静态::)。 解释PHP中的晚期静态绑定(静态::)。 Apr 03, 2025 am 12:04 AM

静态绑定(static::)在PHP中实现晚期静态绑定(LSB),允许在静态上下文中引用调用类而非定义类。1)解析过程在运行时进行,2)在继承关系中向上查找调用类,3)可能带来性能开销。

什么是PHP魔术方法(__ -construct,__destruct,__call,__get,__ set等)并提供用例? 什么是PHP魔术方法(__ -construct,__destruct,__call,__get,__ set等)并提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些?PHP的魔法方法包括:1.\_\_construct,用于初始化对象;2.\_\_destruct,用于清理资源;3.\_\_call,处理不存在的方法调用;4.\_\_get,实现动态属性访问;5.\_\_set,实现动态属性设置。这些方法在特定情况下自动调用,提升代码的灵活性和效率。

See all articles