PHP中异常处理的一些方法整理
这篇文章主要介绍了PHP中异常处理的一些方法整理,尽管PHP并不能说是一种很优秀的语言,但其相关技术依然在不断得到改进,需要的朋友可以参考下
每一个新的功能添加到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 assumed try { $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标准库;开发者就可以以同样的方式在他们的项目中,并且复用这些项目的新的最佳的方法已经出现。