PHP中异常处理的一些方法整理 |
本文标签:PHP,异常 每一个新的功能添加到PHP运行时会创建一个指数随机数,通过这样的方式开发者可以使用和甚至滥用这个新特性 。然而,直到一些好的和坏的使用情况陆续出现开发者们才达成了共识 。当这些新案例不断浮现,我们终于可以辨别出什么是最好或最坏的做法 。 异常处理在PHP中的确无论如何都不算是一个新的特征 。但在本文中,我们将讨论在PHP 5.3中基于异常处理的两个新的特点 。第一个是嵌套异常第二是一套SPL(现在的PHP运行机制的一个核心扩展)的扩展的新的异常类型 。这两个新特性,这本书里都能找到最佳实践值得各位去详细研究 。
背景 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 的子类有几层 ), 他们可以根据自己的需要处理这些异常 。
为什么这些特性很有用?通常,通过使用其他代码来抛出自己的类型的异常是最有效的代码 。这些代码可能是使用适配器模式封装的提供一些适应性更强强的函数的第三方代码库的代码,或利用一些PHP扩展来抛出异常的简单代码 。
下面的示例展示了一个虚构的数据库适配器可能如何去实现嵌入式的异常: 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(及以上)运行代码的人使用 。虽然在编写应用程序层的代码时,看起来不那么重要,但在我们写或者使用代码库时,使用这些新异常类型变得更加重要
这个问题的解决办法显然是通过某种方式对异常进行编码,这样就可以在需要辨别如何对这种异常环境做出反应的时候能够更加容易的查询到 。第一个反应库是使用异常基类的$code属性 。另一个是通过创建可以被抛出且能描述自身行为的子类或者新的异常类 。这两种方法具有相同的明显的缺点 。两者都没有呈现出想这样的最好的例子 。两者都不被认为是一个标准,因此每个试图复制这两种解决方案的项目都会有小的变化,这就迫使使用这需要回到文档以了解所创建的库中已经有的具体解决方案 。现在通过使用SPL的新的类型方法,也称作php标准库;开发者就可以以同样的方式在他们的项目中,并且复用这些项目的新的最佳的方法已经出现 。
所以我如何去使用它们,就用这些让人无语的密密麻麻的细节描述? 现在在SPL中有总共13个新的异常类型 。其中两个可被视为基类:逻辑异常和运行时异常;两种都继承php异常类 。其余的方法在逻辑上可以被拆分为3组:动态调用组,逻辑组和运行时组 。 动态调用组包含异常 BadFunctionCallException和BadMethodCallException,BadMethodCallException是BadFunctionCallException(LogicException的子类)的子类,这意味着这些异常可以被其直接类型(译者注:就是异常自身的类型,大家都知道异常有很多种)、LogicException,或者Exception抓到(译者注:就是catch)你应该在什么时候使用这些?通常,你应该在由一个无法处理的__call()方法产生的情况,或者回调无法不是一个有效的函数(简单说,当某些东西并非is_callable())时使用 。 例如: // OO variant class Foo { public function __call($method, $args) { switch ($method) { case doBar: /* ... */ break; default: throw new BadMethodCallException(Method . $method . is not callable by this object); } } } // procedural variant function 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客户端/服务端能够发送和解释的请求 。
最后一组是运行时(runtime )组 。它由OutOfBoundsException、OverflowException、RangeException、UnderflowException、UnexpectedValueExceptio组成 。这些异常也是RuntimeException的子类,当然也是PHP的Exception的子类 。在“运行时”(runtime)的函数、方法发生异常时,这些异常(运行时组)会被调用
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 brevity namespace 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. 这为捕捉你的类库组件中的异常提供了极大的便利. 此外, 通过分析异常的类型, 我们也能看出某个异常的含义. |