侯体宗的博客
  • 首页
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

PHP中异常处理的一些方法整理

php  /  管理员 发布于 8年前   161

每一个新的功能添加到PHP运行时会创建一个指数随机数,通过这样的方式开发者可以使用和甚至滥用这个新特性。然而,直到一些好的和坏的使用情况陆续出现开发者们才达成了共识。当这些新案例不断浮现,我们终于可以辨别出什么是最好或最坏的做法。

异常处理在PHP中的确无论如何都不算是一个新的特征。但在本文中,我们将讨论在PHP 5.3中基于异常处理的两个新的特点。第一个是嵌套异常第二是一套SPL(现在的PHP运行机制的一个核心扩展)的扩展的新的异常类型。这两个新特性,这本书里都能找到最佳实践值得各位去详细研究。


特别要注意:这些特性中的一些已经存在于低于5.3的PHP版本之中,或者至少能够在低于5.3的版本之中被实现.  而当本文提到 PHP 5.3, 并不是严责意义上的 PHP 运行时版本. 相反,它意味着代码库和项目是采用 PHP 5.3 作为最低版本的,但同时也是在新的发展阶段出现的所有最佳实践.  这个发展阶段所凸显的是特定的几个像Zend Framework, Symfony, Doctrine 以及 PEAR 这样的项目所进行的“2.0”尝试.

背景

PHP 5.2  只有一个异常类 Exception。按照 Zend Framework / PEAR 的开发标准, 这个类是你的库中所有异常类的基类。如果你创建一个名叫 MyCompany 的库,按 Zend Framework / PEAR 的标准, 库中所有的代码文件都会以 MyCompany_ 开头。要是你想给库创建自己的异常基类: MyCompany_Exception, 那就用该类继承 Exception,然后再由组件(component )继承和抛出该异常类。比如你有一个组件 MyCompany_Foo,你可以给它创建一个用在该组件内部的异常基类 MyCompany_Foo_Exception。这些异常能被捕捉 MyCompany_Foo_Exception,MyCompany_Exception 或 Exception 的代码捉到。 对于库中其他用到该组件的代码来说,这是个三层的异常(或更多,取决于 MyCompany_Foo_Exception 的子类有几层 ), 他们可以根据自己的需要处理这些异常。


在php5中,基本异常类已经支持嵌套的特性了。什么是嵌套呢?嵌套是一种能力可以去捕获特殊异常,或者捕获参照原始异常而创建的一个新的异常对象。这将会允许caller属性在更公开的类型的开销库中出现的两种异常类上得到体现,当然也会在具有原始异常行为的异常类上体现。

为什么这些特性很有用?通常,通过使用其他代码来抛出自己的类型的异常是最有效的代码。这些代码可能是使用适配器模式封装的提供一些适应性更强强的函数的第三方代码库的代码,或利用一些PHP扩展来抛出异常的简单代码。


例如,在组件 Zend_Db 中, 它使用了适配器模式来封装特定的 PHP 扩展,来创建一个数据库抽象层.  在一个适配器中, Zend_Db 封装了 PDO, 而 PDO 会抛出它自己的异常 PDOException, Zend_Db 需要捕获这些特定于 PDO 的异常,并让它们以可预期且类型已知的 Zend_Db_Exception 重新被抛出. 这样就给了开发者保证, Zend_Db 将总是抛出 Zend_Db_Exception 类型的异常(因此可以被捕获), 而他们同时也可以在需要的时候访问到最开始被抛出的 PDOException.

下面的示例展示了一个虚构的数据库适配器可能如何去实现嵌入式的异常:
 

class MyCompany_Database{ /**  * @var PDO object setup during construction  */ protected $_pdoResource = null;   /**  * @throws MyCompany_Database_Exception  * @return int  */ public function executeQuery($sql) {  try {   $numRows = $this->_pdoResource->exec($sql);  } catch (PDOException $e) {   throw new MyCompany_Database_Exception('Query was unexecutable', null, $e);  }  return $numRows; } }

为了使用嵌入式的异常,你就得调用被捕获异常的getPrevious()方法:
 

// $sql and $connectionParameters assumedtry { $db = new MyCompany_Database('PDO', $connectionParams); $db->executeQuery($sql);} catch (MyCompany_Database_Exception $e) { echo 'General Error: ' . $e->getMessage() . "\n"; $pdoException = $e->getPrevious(); echo 'PDO Specific error: ' . $pdoException->getMessage() . "\n";}

大多数最近被实现的PHP扩展都拥有OO(面向对象)接口.  因此,这些API倾向于抛出异常,而不是发生错误终止。PHP中能够抛出异常的扩展,稍微列举出几个就包括有PDO, DOM, Mysqli, Phar, Soap 以及 SQLite.

新特性:新核心异常类型

在PHP 5.3开发中,我们展示了一些有趣的新异常类型。这些异常在PHP 5.2.x中已经存在,但最近还没到“重新评估”异常的最佳实践,现在他们会显得更加引人注目。他们在SPL扩展中得以应用,并在手册中列出(这里)由于这些新的异常类型是PHP核心的一部分,也是SPL的一部分,它们可以被任何用PHP 5.3(及以上)运行代码的人使用。虽然在编写应用程序层的代码时,看起来不那么重要,但在我们写或者使用代码库时,使用这些新异常类型变得更加重要


那么为什么新异常是普通类型?以前,开发者试图通过在异常消息提醒中放入更多的内容来赋予异常更多的含义。虽然这样做是可行的,但是它有几个缺点。一是你无法捕获基于消息的异常。这可是一个问题,如果你知道一组代码是同样的异常类型与不同的提示消息对应不同异常情况下,处理起来的难度将相当的大。例如,一个认证类,在对$auth->authenticate();;它抛出异常的相同类型的(假设是异常),但不同的消息对应两个具体的故障:产生故障原因是认证服务器不能达到但是相同的异常类型却提示失败的验证消息不同。在这种情况下(注意,使用异常可能不是处理认证响应最好的方式),这将需要用字符串来解析消息从而处理这两种不同的情况。

这个问题的解决办法显然是通过某种方式对异常进行编码,这样就可以在需要辨别如何对这种异常环境做出反应的时候能够更加容易的查询到。第一个反应库是使用异常基类的$code属性。另一个是通过创建可以被抛出且能描述自身行为的子类或者新的异常类。这两种方法具有相同的明显的缺点。两者都没有呈现出想这样的最好的例子。两者都不被认为是一个标准,因此每个试图复制这两种解决方案的项目都会有小的变化,这就迫使使用这需要回到文档以了解所创建的库中已经有的具体解决方案。现在通过使用SPL的新的类型方法,也称作php标准库;开发者就可以以同样的方式在他们的项目中,并且复用这些项目的新的最佳的方法已经出现。


第二个缺点是使用详细信息的做法使得理解这些异常情况对那些非英语或英语能力有限的开发者来说十分困难。这可能会使的开发者在试图理解异常信息的含义的过程十分的缓慢。许多开发者也会写关于异常的文章,因为还未出现一个统一的整合过的标准所要有同这些开发者数量相同的不同的版本来描述异常消息所描述的情况。

所以我如何去使用它们,就用这些让人无语的密密麻麻的细节描述?

现在在SPL中有总共13个新的异常类型。其中两个可被视为基类:逻辑异常和运行时异常;两种都继承php异常类。其余的方法在逻辑上可以被拆分为3组:动态调用组,逻辑组和运行时组。

动态调用组包含异常 BadFunctionCallException和BadMethodCallException,BadMethodCallException是BadFunctionCallException(LogicException的子类)的子类,这意味着这些异常可以被其直接类型(译者注:就是异常自身的类型,大家都知道异常有很多种)、LogicException,或者Exception抓到(译者注:就是catch)你应该在什么时候使用这些?通常,你应该在由一个无法处理的__call()方法产生的情况,或者回调无法不是一个有效的函数(简单说,当某些东西并非is_callable())时使用。

例如:
 

// OO variantclass Foo{ public function __call($method, $args) {  switch ($method) {   case 'doBar': /* ... */ break;   default:    throw new BadMethodCallException('Method ' . $method . ' is not callable by this object');  } } } // procedural variantfunction foo($bar, $baz) { $func = 'do' . $baz; if (!is_callable($func)) {  throw new BadFunctionCallException('Function ' . $func . ' is not callable'); }}

一个直接的例子,在__call时call_user_func()。这组异常在开发各种API动态方法的调用、函数调用时非常有用,例如这是一个可以被SOAP和XML-RPC客户端/服务端能够发送和解释的请求。


第二组是逻辑(logic )组。这组由DomainException、InvalidArgumentException、LengthException、OutOfRangeException组成。这些异常也是LogicException的子类,当然也是PHP的Exception的子类。在有状态不定,或者错误的方法/函数的参数时使用这些异常。为了更好地理解这一点,我们先看看最后一组异常

最后一组是运行时(runtime )组。它由OutOfBoundsException、OverflowException、RangeException、UnderflowException、UnexpectedValueExceptio组成。这些异常也是RuntimeException的子类,当然也是PHP的Exception的子类。在“运行时”(runtime)的函数、方法发生异常时,这些异常(运行时组)会被调用


逻辑组和运行时组如何一起工作?如果你看看对象的剖析,通常是发生的是两者之一。首先,对象将跟踪并改变状态。这意味着对象通常是不做任何事情。它可能会传递结构给它,它可能会通过setter和getter设置一些东西(译者注:例如$this->foo='foo'),或者,它可能会引用其他对象。第二,当对象不跟踪或改变状态,这代表正在操作――做它该做的事。这是对象的运行时(runtime)。例如,在对象的一生中,它可能被创建,设置一些东西,那么它可能会被setFoo($foo),setBar($bar)。在这些时候,任何类型的LogicException应该被提高。此外,当对象内的方法被带参数调用时,例如$object->doSomething($someVariation);在前几行检查$someVariation变量时,可能抛出一个LogicException。完成检查$someVariation后,它继续做它该做的doSomething(),这时被认为是它的“运行时”(runtime),在这段代码中,可能抛出RuntimeExcpetions异常。


要理解得更好,我们来看看这个概念在代码中的运用:
 

class Foo{ protected $number = 0; protected $bar = null;  public function __construct($options) {  /** 本方法抛出LogicException异常 **/ }   public function setNumber($number) {  /** 本方法抛出LogicException异常 **/ }   public function setBar(Bar $bar) {  /** 本方法抛出LogicException异常 **/ }   public function doSomething($differentNumber) {  if ($differentNumber != $expectedCondition) {   /** 在这里,抛出LogicException异常 **/  }     /**   * 在这里,本方法抛出RuntimeException异常   */  } }

现在理解了这一概念,那么,对代码库的使用者来说,这是做什么的呢?使用者可以随时确定对象的异常状态,他们可以用异常的具体的类型来捕获(catch)异常,例如InvalidArgumentException或LengthException,至少也是LogicException。通过这种级别的精度调整,和类型的多样,他们可以用LogicException捕获最小的异常,但也可以通过实际的异常类型获得更好的理解。同样的概念也适用于运行时的异常,可以抛出更多的特定类型的异常,并且不论是特定或非特定类型的异常,都可以被捕获(catch)。它可以给使用者提供更详细的情况和精确度。

下面是一个关于SPL异常的表,您可能会有兴趣

类库代码中的最佳实践

PHP 5.3 带来了新的异常类型, 同时也带给我们新的最佳实践. 除了将某些特定的异常(如: InvalidArgumentException, RuntimeException)标准化外, 捕捉组件级的异常, 也很重要. 关于这方面, ZF2 wiki 和 PEAR2 wiki 上面有深入的探讨.

简而言之, 除了上面提到的各种最佳实践, 我们还应该用 Marker Interface 来创建一个组件级的异常基类. 通过创建组件级的 Marker Interface, 用在组件内部的异常既能继承 SPL 的异常类型, 也能在运行时被各种代码捕捉. 我们来看下列代码:
 

// usage of bracket syntax for brevitynamespace MyCompany\Component {  interface Exception {}  class UnexpectedValueException   extends \UnexpectedValueException   implements Exception {}  class Component {  public static function doSomething()  {   if ($somethingExceptionalHappens) {    throw new UnexpectedValueException('Something bad happened');   }  } } }

如果调用上面代码中的 MyCompany\Component\Component::doSomething() 函数, doSomething() 抛出的异常可以当作下列异常类型捕捉: PHP 的 Exception, SPL 的 UnexpectedValueException, SPL 的 RuntimeException, 该组件的MyCompany\Component\UnexpectedValueException, 或该组件的 MyCompany\Component\Exception. 这为捕捉你的类库组件中的异常提供了极大的便利. 此外, 通过分析异常的类型, 我们也能看出某个异常的含义.

您可能感兴趣的文章:

  • php中try catch捕获异常实例详解
  • PHP异常Parse error: syntax error, unexpected T_VAR错误解决方法
  • PHP的异常处理类Exception的使用及说明
  • php Try Catch异常测试
  • PHP如何抛出异常处理错误
  • php中异常处理方法小结
  • PHP中的错误处理、异常处理机制分析
  • 实例讲解如何在PHP的Yii框架中进行错误和异常处理
  • php异常:Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE eval()''d code error
  • PHP批斗大会之缺失的异常详解


  • 上一条:
    列举PHP的Yii 2框架的开发优势
    下一条:
    在PHP程序中使用Rust扩展的方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 用Time Warden监控PHP中的代码处理时间(0个评论)
    • 在PHP中使用array_pop + yield实现读取超大型目录功能示例(0个评论)
    • Property Hooks RFC在PHP 8.4中越来越接近现实(0个评论)
    • 近期文章
    • 在go语言中实现字符串可逆性压缩及解压缩功能(0个评论)
    • 使用go + gin + jwt + qrcode实现网站生成登录二维码在app中扫码登录功能(0个评论)
    • 在windows10中升级go版本至1.24后LiteIDE的Ctrl+左击无法跳转问题解决方案(0个评论)
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-11
    • 2017-12
    • 2018-01
    • 2018-02
    • 2018-03
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-09
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-09
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客