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 内置错误和异常类型如下表所示:

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

异常处理

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

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

捕获异常

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

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

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

现在我们在 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 语句块中的内容。这样,当我们再次执行代码时,就会捕获这个异常:

如果你不知道抛出的异常类型是什么,可以通过 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);

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

未处理异常会转化为 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 时,由于捕获的异常抛给了上一层,但目前没有上一层调用代码,也就变成了未处理异常,最终这些异常会通过全局异常处理器进行兜底处理,执行上述代码,输出如下:

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

自定义异常类

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

class IndexNotExistsException extends LogicException

{

}

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

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

另外,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;

}

}

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

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

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

小结

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

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

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

  1. think.class.php错误,thinkphp源码分析(四)—错误及异常处理篇

    源码分析 错误及异常处理机制 错误及异常处理机制文件是/thinkphp/library/think/Error.php,在框架引导文件的的基础文件base.php中注册(不知道的可以去看<&l ...

  2. PHP常用功能块_错误和异常处理 — php(32)

    一.错误和异常处理 1.1 错误类型和基本的调试方法 PHP程序的错误发生一般归属于下列三个领域: 语法错误: 语法错误最常见,并且也容易修复.如:代码中遗漏一个分号.这类错误会阻止脚本的执行. 运行 ...

  3. PHP加密时遇到try错误,深入学习PHP错误与异常处理

    一.PHP异常处理机制 由于我的工作岗位性质,我绝大部分的开发工作涉及到的操作风险都非常高,而且很频繁地使用其他部门提供的接口.所以,对于程序中可能出现的异常和错误都要有相应的处理方法,否则遗漏的话会 ...

  4. PHP如何进行错误与异常处理(PHP7中的异常处理和之前版本异常处理的区别)

    PHP如何进行错误与异常处理(PHP7中的异常处理和之前版本异常处理的区别) 一.总结 一句话总结: throwable接口+Error类 在PHP7更新中有一条:更多的Error变为可捕获的Exce ...

  5. python错误-python错误和异常处理怎处理你知道么

    原标题:python错误和异常处理怎处理你知道么 异常处理 什么是异常? 首先要清楚,什么是异常,异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常 ...

  6. python中错误和异常处理

    错误和异常处理 在python中一共有2种错误:一种是语法错误,另外一种是异常. 语法错误 语法错误也叫做解析错误,是指python无法正确的识别代码的造成的.根本原因在于人的行为:手残,脑残和眼残的 ...

  7. Python 迭代器,错误、异常处理

    迭代器 迭代器可以用来遍历字符串.列表.元组.集合.字典. myString="hello" myIter=iter(myString) ##iter()函数可以获取元素集的一个迭 ...

  8. ThinkPHP6项目基操(13.实战部分 项目中的自定义异常处理总结 错误页面API错误)

    项目中的自定义异常处理总结 错误页面&API错误 前言 一.异常分类 1. 控制器找不到 2. 方法找不到 3. 请求资源不存在 4. 系统內部异常.HTTP异常等 二.异常处理 1. 前置处 ...

  9. Golang错误和异常处理的正确姿势

    Golang错误和异常处理的正确姿势 错误和异常是两个不同的概念,非常容易混淆.很多程序员习惯将一切非正常情况都看做错误,而不区分错误和异常,即使程序中可能有异常抛出,也将异常及时捕获并转换成错误.从 ...

最新文章

  1. 服务器磁盘阵列做win7系统,Raid0可以安装winxp-x86,但不能安装win7-x64,是怎么回事呢?!...
  2. OpenCV norm 计算范数(18)
  3. 单例模式的3种实现方式, 及其性能对比
  4. POJ3270 Cow Sorting ——置换群
  5. 图像处理中常用数学知识
  6. Linux 的 Shell 变量
  7. (转)AS3 面相对象 高级话题
  8. 1块钱,能买来财富自由吗?
  9. CentOS 7.5基于Docker部署4.2 版本的zabbix监控平台
  10. 计算机系统基础知识——详解二进制正负数及补码设计
  11. VUE3+TS 生成一些五线谱
  12. Unity 自定义扩展Hierachy右键菜单
  13. 档案系列包括图书馆管理与服务器,基于Web的图书馆档案管理系统设计与实现.pdf...
  14. 道法演讲之马云从事教育
  15. 【秋无痕作品】Windows7SP1(32位)+XPSP3集成安装版V201108
  16. 未来计算机作文600字,未来的电脑作文600字(精选3篇)
  17. verilog语言实现全加器
  18. sql server数据库卡问题排查
  19. 从头开始写STM32F103C8T6驱动库(一)——STM32CubeMX创建并调整工程结构
  20. python识别图片中的物体_python3+opencv3识别图片中的物体并截取的方法

热门文章

  1. 网络克隆软件_网文生成器,克隆的是骗钱“病毒”
  2. linux 直接映射 页表大小,linux 启动过程临时页表到底映射了多大内存?
  3. 木板最优切割利润最大_最多进行K笔交易的股票最大买卖利润
  4. python函数示例_带Python示例的float()函数
  5. nodejs在Liunx上的部署生产方式-PM2
  6. 自适应阈值算法(大津阈值法)
  7. pythonweb框架_浅谈python web三大框架
  8. linux系统怎么设置开机密码,Linux_Linux系统怎么设置开机密码?Linux设置开机密码的方法,为了保证Linux系统的安全,应 - phpStudy...
  9. 口腔取模过程及注意事项_取模变形?教你三种方法,轻松防止取模变形!
  10. java @valid 密码不一致_一个成熟的Java项目如何优雅地处理异常