php面向对象异常处理,PHP 错误和异常处理(下)

PHP 错误和异常处理(下)

由 学院君 创建于9个月前, 最后更新于 7个月前

版本号 #1

1723 views

2 likes

0 collects

上篇我们讲了 PHP 中的错误报告和捕获,今天,我们来看看 PHP 程序中的异常处理。

错误 vs. 异常

错误与异常可以看作一对孪生兄弟,从严格的面向对象编程角度来说,错误指的是致命错误(Fatal Error,比如编译错误和语法错误),出现运行时错误后,程序应该无法继续往后执行,需要执行一些清理工作并记录日志后退出当前处理流程。

而异常指的是程序中出现的可预测的、可恢复的中轻度问题,比如数空对象引用、文件不存在、除数为零、数组越界等,当程序运行时出现异常后,我们可以对其进行捕获,或者抛给上层的业务代码处理,和错误报告类似,如果通过 set_exception_hanlder 函数定义了全局异常处理器,则所有未处理异常会集中到这里处理,如果没有定义任何处理异常的代码,最终会抛出一个 Fatal Error(也就是说,所有未处理异常都会被当作错误进行兜底处理)。程序出现异常后,应该可以继续往后执行。

但是我们在 PHP 中可以看到两者的边界并不明显,因为异常是 PHP 5 之后实现完整面向对象机制后引入的,之前的 PHP 中只有错误,没有异常,所以你可以看到那么多的错误级别,比如 Notice、Warning、Deprecated 这些中轻度错误,实际上完全可以通过异常进行处理。

层次结构

在 PHP 7 中,所有错误都归属于 Error 类,所有异常都归属于 Exception 类,两者是并列关系,并且最新 PHP 内置错误和异常类型如下表所示:

d9bff10f53719fd348a406c0a95a8fb8.png

而 Error 和 Exception 类又都实现了 Throwable 接口。

异常处理

有了以上的了解,大家应该大体上明白了异常是怎么回事以及所处的位置,接下来,我们来看看如何处理异常,我们按照三个层级递进:首先是在定义代码的地方捕获并处理,然后是在上层调用的地方捕获并处理,以及定义全局异常处理器处理。

在 php_learning/oop 目录下新建 exception.php 保存本篇教程的代码。

捕获异常

首先来看如何在代码定义的地方捕获异常,和错误捕获一样,我们可以 try...catch... 语句块捕获异常。

在 exception.php 中编写一段测试代码:

ae62700eeb18477c86bdada650971a47.png

我们试图从 $book 数组中访问一个不存在的索引,此时没有定义任何异常捕获和处理逻辑,所以会以错误报告方式进行兜底处理:

31ad84d738e245076905b128b743c811.png

现在我们在 getItemFromBook 方法中会参数进行验证,如果不满足要求则抛出异常:

function getItemFromBook($book, $key)

{

if (empty($book) || !key_exists($key, $book)) {

throw new InvalidArgumentException("数组为空或者对应索引不存在!");

}

return $book[$key];

}

通过 throw 关键字即可抛出异常,这里我们通过 new 关键字实例化了一个内置的 InvalidArgumentException 异常对象作为返回值抛出。

抛出异常后会终止后续代码的执行,然后我们可以在调用的地方通过 try/catch 对这个异常进行捕获:

try {

$val = getItemFromBook($book, 'desc');

} catch (InvalidArgumentException $exception) {

echo $exception->getMessage();

exit();

}

var_dump($val);

其原理是当 try 语句块中遇到异常后,会通过 catch 语句进行捕获,如果抛出的异常和声明异常类型匹配,则执行 catch 语句块中的内容。这样,当我们再次执行代码时,就会捕获这个异常:

38b330d07cba8f641565a0191c923537.png

如果你不知道抛出的异常类型是什么,可以通过 Exception 基类捕获(或者其他父级异常类),也就是说,此处也符合父子类型的转化逻辑:

try {

$val = getItemFromBook($book, 'desc');

} catch (Exception $exception) {

echo $exception->getMessage();

exit();

}

var_dump($val);

但是如果不是 InvalidArgumentException 或者其父类,就不能捕获了:

try {

$val = getItemFromBook($book, 'desc');

} catch (RuntimeException $exception) {

echo $exception->getMessage();

exit();

}

var_dump($val);

执行上述代码,打印结果如下:

b69a7952dab0b74645a9dd230f811b7f.png

未处理异常会转化为 Fatal Error 处理。

如果调用程序抛出了多个异常:

function getItemFromBook($book, $key)

{

if (empty($book)) {

throw new InvalidArgumentException("数组为空!");

}

if (!key_exists($key, $book)) {

throw new OutOfBoundsException("对应索引不存在!");

}

return $book[$key];

}

可以通过多个 catch 语句进行捕获:

try {

$val = getItemFromBook($book, 'desc');

} catch (InvalidArgumentException $exception) {

echo $exception->getMessage();

exit();

} catch (OutOfBoundsException $exception) {

echo $exception->getMessage();

exit();

}

var_dump($val);

但是由于我们在每个 catch 分支里面都调用 exit() 退出程序,可以通过添加 finally 语句块定义一个兜底逻辑:

$exit = false;

try {

$val = getItemFromBook($book, 'desc');

} catch (InvalidArgumentException $exception) {

echo $exception->getMessage() . PHP_EOL;

$exit = true;

} catch (OutOfBoundsException $exception) {

echo $exception->getMessage() . PHP_EOL;

$exit = true;

} finally {

$exit ? exit() : var_dump($val);

}

不管 try 语句块中的代码是否抛出异常,finally 语句块中的代码都会执行,如果抛出异常,则会先执行 catch 语句块中的代码,再执行 finally 语句块中的代码,否则会直接执行 finally 语句块中的代码。

抛出异常

我们也可以在捕获到异常后不进行处理,直接抛出,交给上一层调用代码进行进一步处理:

try {

$val = getItemFromBook([], null);

$val = getItemFromBook($book, 'desc');

} catch (InvalidArgumentException $exception) {

throw $exception;

} catch (OutOfBoundsException $exception) {

throw $exception;

} finally {

var_dump($val);

}

上一层的处理逻辑也无非是进行 try...catch... 捕获后进行处理或者继续抛出。

全局异常处理器

在进行系统框架设计时,考虑到系统的稳健型,总会有一些异常的「漏网之鱼」没有被捕获和处理,这个时候就要通过 set_exception_handler 函数注册全局的异常处理器来处理这些未被捕获和处理的异常:

...

function myExceptionHandler(Exception $exception)

{

echo 'Uncaught Exception [' . get_class($exception) . ']: ' . $exception->getMessage() . PHP_EOL;

echo 'Thrown in ' . $exception->getFile() . ' on line ' . $exception->getLine() . PHP_EOL;

}

set_exception_handler('myExceptionHandler');

try {

$val = getItemFromBook($book, 'desc');

} catch (InvalidArgumentException $exception) {

throw $exception;

} catch (OutOfBoundsException $exception) {

throw $exception;

} finally {

if (isset($val)) {

var_dump($val);

} else {

echo '异常将通过全局异常处理器处理...' . PHP_EOL;

}

}

我们首先需要定义一个自定义的 myExceptionHandler 函数作为全局异常处理器,在这个函数中,我们需要传入异常对象作为参数,然后输出该异常类名、消息、出现异常的文件和行号,最后通过 set_exception_handler 函数将其注册为全局异常处理器。

在后续调用 getItemFromBook 时,由于捕获的异常抛给了上一层,但目前没有上一层调用代码,也就变成了未处理异常,最终这些异常会通过全局异常处理器进行兜底处理,执行上述代码,输出如下:

29fc4429b831f432c887b50d09c705d9.png

这里是将异常信息输出到了标准输出(STDOUT),如果是在线上生产环境,和自定义的全局错误处理器一样,你也可以将这些信息记录到日志文件中,或者发送到第三方日志处理服务。

自定义异常类

上面所有的异常都是 PHP 内置的异常类,除此之外,我们也可以根据需要创建自定义的异常类,只需要继承自 Exception 基类或者其子类即可,比如我们为索引不存在定义一个独立的异常类,并且继承自 LogicException 父类:

class IndexNotExistsException extends LogicException

{

}

暂时不需要编写任何方法,它可以继承祖先类 Exception 的所有 protected/public 方法和属性:

4836e2e6b484648d668d901446e35334.png

需要注意的是,Exception 类中的很多方法定义前面都有一个 final 关键字,通过该关键字修饰的方法不能被子类重写,如果我们试图这么做会报错:

3caad4d6e9793d9e56cd34d26b086033.png

另外,final 还可以用于修饰类,通过 final 修饰的类将不能被子类继承。

定义好自定义类之后,就可以在代码中捕获和处理了:

function getItemFromBook($book, $key)

{

...

if (!key_exists($key, $book)) {

throw new IndexNotExistsException("对应索引不存在!");

}

...

}

...

try {

$val = getItemFromBook($book, 'desc');

} catch (InvalidArgumentException $exception) {

throw $exception;

} catch (IndexNotExistsException $exception) {

throw $exception;

} finally {

if (isset($val)) {

var_dump($val);

} else {

echo '异常将通过全局异常处理器处理...' . PHP_EOL;

}

}

执行上述代码,输出结果如下:

06cc3c9279d0ebe43714a149b346495c.png

说明自定义异常类已经可以正常使用。

在实际项目开发中,可以结合自定义异常类和上述异常处理方式构建自己的异常处理体系。

小结

关于 PHP 面向对象编程我们就简单介绍到这里,通过前面的介绍,相信你已经对类和对象的实例化,类级别的静态方法,类功能的垂直扩展(继承、抽象类、接口)和水平扩展(对象组合、Trait)有了充分的认识,此外,PHP 类还支持特有的魔术方法,合理使用这些魔术方法可以进行一些很方便的初始化/善后清理工作,最后,对于程序中出现的错误和异常,可以通过一系列内置的机制进行捕获和处理。

下篇教程,我们将开始介绍 PHP 中如何连接 MySQL 数据库并进行增删改查操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558043.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php设计者,PHP设计者---composer

Composer 是 PHP5以上 的一个依赖管理工具。它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们。Composer 不是一个包管理器。是的,它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理&#…

java内部类练习题,学习笔记——Java内部类练习题

1.尝试在方法中编写一个匿名内部类。package com.lzw;public class AnonymityInnerClass {}class OuterClass4{public OutInterface doit(final String s){return new OutInterface(){private int i0;public int getValue(){System.out.println(s);return i;}public void f(){S…

php左边按钮右边显示,右侧操作按钮

## 方法原型:addRightButton($type , $title , $url , $linkType link, $attr [])> 关于按钮的详细说明请参考[按钮操作](./按钮操作.md)一节。> 注意: 添加操作按钮必须在添加列时添加操作栏一列, 否则即使加了右侧按钮也不会显示## 参数说明&#xff1…

电脑运行java游戏,电脑运行软件卡顿?这几招游戏或是办公,让你速度飞起!...

许多朋友想知道他们已经购买了高端计算机,但是无论玩游戏还是运行软件,他们仍然陷于困境。今天,让我们教大家一个简单的方法来提高计算机性能。焦点:此方法大大提高了用户对高度配置的计算机的影响。如果计算机的设置不太高&#…

c matlab 数据类型,matlab中数据类型与c语言数据类型的区别?

matlab中数据类型与c语言数据类型的区别?mip版 关注:233 答案:3 悬赏:0解决时间 2021-02-23 08:46已解决2021-02-22 22:32matlab中数据类型与c语言数据类型的区别?最佳答案2021-02-22 22:42简单谈一下。1.好多类型是对应的。输入一个数,ma…

mie散射粒子消光系数 matlab代码,一种球形粒子GMie散射效率极值的算法

一种球形粒子G Mie散射效率极值的算法【专利摘要】球形粒子的G?Mie散射问题属于物理光学领域,涉及光与原子的相互作用。G?Mie理论在有着广泛的用途,如分析激光衍射粒度,光镊,激光制冷,大气理论和温室效应分析等。目前…

oracle util_mail,Oracle UTL_MAIL邮件包程序使用实践

在《使用包实现存储过程邮件发送》(http://space.itpub.net/17203031/viewspace-700327)中,笔者已经初步介绍了使用UTL_MAIL包实现系统邮件发送功能。UTL_MAIL包较传统的PL/SQL邮件发送已经有很大改进和简化。但是对于一般系统而言,邮件功能设置(如发送、…

oracle+内存错误,oracle使用内存的错误,ORA-27102: out of memory

网上参阅资料,整理到这里,以备查阅。主要由于内存的分配问题,导致oracle数据库启动失败。数据库关闭之后重新启动,出现下面的错误:SQL> startup;ORA-27102: out of memoryLinux-x86_64 Error: 28: No space left on…

oracle连接数达不到上限,Oracle超出最大连接数问题及解决(转)

用过Oracle的应该都熟悉如何查看和设置Oracle数据库的最大连接数。这里就再啰嗦一遍。查看当前的连接数,可以用select count(*) from v$process;设置的最大连接数(默认值为150)select value from v$parameter where name ‘processes’;修改最大连接数alter system …

oracle批量把小写转换成大写,oracle数据库批量将表名小写转大写

ORACLE10g数据库,Sqlserver通过DTS导出数据到ORACLE后。查询表数据时输入:select * from "表名" ;查询数据执行正常。如果输入:select * from 表名;提示ORA-000942表或视图不存在后来发现原因是:导入oracle数…

oracle数据库安装提示M,Python第13课:oracle数据库的安装

Python第13课:oracle数据库的安装时间 2019-01-22下午4:30主讲 罗恒丰地点 四楼电教室版本:11.2大小:2.06G安装文件在专班ftp里python安装文件的文件夹。一.找到 stage/cvu/cvu_prereq.xml ,编辑系统名,以适…

linux 文件怎么不让删,请问如何设置权限,可以禁止用户删除文件

原帖由 WHITLACK 于 2009-9-28 08:48 发表 针对某个文件,如何设置权限,可以禁止删除?文件权限的r-w-x好像不能禁止删除的啊,谢谢指教!1:使用粘滞位可以做到,下面是介绍.强制位与冒险位、粘滞位针对u,g&…

linux查看etl进程,常见ETL工具

【国外】datastage点评:最专业的ETL工具,价格不菲,使用难度一般-------------&#xf…

linux运行中望cad,国产CAD软件中望的Linux版适配UOS, 我在国产系统里试了试

可能大家都知道,以往我们在国产操作里运行的软件,很多都是国外开源的软件。在以前很长一段时间里,国产操作中,国内企业很少去适配的。据说,国产深度之所以有很多国产软件,其中一个原因就是他们一家一家去拜…

linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan

还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在了double_rq_lock函数上,而double_rq_lock则是load_balance触发的,怀疑当时的核间…

linux适应环境,Linux从入门到适应(一):VSCode C++环境配置

作为在Windows环境下习惯使用Visual Studio IDE的人,对于Linux环境下的Vim编辑使用十分难受,虽然网上很多人说vim非常牛逼和强大,但是我更加习惯于使用VS code的界面,所以我选择VS code作为编辑器使用。VS code本身是一个编辑器&a…

linux指令能回滚么,如何在Ubuntu上撤消或回滚“ apt-get upgrade”命令?

我今天必须在Debian系统上执行此操作。首先,我确定了有问题的升级发生的时间范围,并检索了给出升级包的新旧版本号的日志条目:$ awk $1"2016-03-20" && $3"upgrade" /var/log/dpkg.log2016-03-20 16:58:22 upgra…

linux文件名过长无法删除,不能删除文件,出现“源文件名长度大于系统支持的长度...

有时候删除windows中的目录的时候,会出现"源文件名长度大于系统支持的长度", 而导致不能删除, 作为一个程序猿, 怎么可以被这个折服呢, 上代码:import java.io.File;/*** author 吴庆龙* 2015年10月13日 下午1:22:33*/public class DeleteFiles {public static void …

cpu system linux,LINUX system BOOT

64位处理器指的是CPU GPRs(General-Purpose Registers 通用寄存器)的数据宽度为64位计算机在接通电源的瞬间,CPU处于16位实模式,20位的寻址空间,即1MB,此时内存里没有任何数据对CPU来讲,系统中所有的存储器(含BIOS的RO…

linux下IPROTO_TCP,TCP/IP协议栈在Linux内核中的运行时序分析

可选题目三:TCP/IP协议栈在Linux内核中的运行时序分析在深入理解Linux内核任务调度(中断处理、softirg、tasklet、wq、内核线程等)机制的基础上,分析梳理send和recv过程中TCP/IP协议栈相关的运行任务实体及相互协作的时序分析。编译、部署、运行、测评、…