>  기사  >  백엔드 개발  >  PHP_php 기술의 예외 처리 방법

PHP_php 기술의 예외 처리 방법

WBOY
WBOY원래의
2016-05-16 20:11:52914검색

PHP 런타임에 추가된 모든 새로운 기능은 기하급수적인 난수를 생성하므로 개발자는 이 새로운 기능을 사용하거나 남용할 수도 있습니다. 그러나 몇 가지 좋은 사용 사례와 나쁜 사용 사례가 나타나기 전까지는 개발자들이 합의에 도달하지 못했습니다. 이러한 새로운 사례가 등장하면서 우리는 마침내 최선의 접근 방식과 최악의 접근 방식이 무엇인지 식별할 수 있습니다.

예외 처리는 실제로 PHP의 새로운 기능이 아닙니다. 하지만 이 기사에서는 PHP 5.3의 예외 처리를 기반으로 하는 두 가지 새로운 기능에 대해 설명합니다. 첫 번째는 중첩된 예외이고 두 번째는 SPL(현재 PHP 운영 메커니즘의 핵심 확장)에 의해 확장된 새로운 예외 유형 세트입니다. 이 두 가지 새로운 기능에 대한 모범 사례는 이 책에서 찾을 수 있으며 자세히 연구할 가치가 있습니다.


특별 참고 사항: 이러한 기능 중 일부는 이미 5.3 이전 버전에 존재하거나 적어도 5.3 이전 버전에서 구현될 수 있습니다. 이 기사에서 언급하는 PHP 5.3은 엄밀한 의미에서는 PHP 런타임 버전이 아닙니다. 이는 코드 베이스와 프로젝트가 PHP 5.3을 최소 버전으로 사용하고 있음을 의미할 뿐만 아니라 새로운 개발 단계에서 나타나는 모든 모범 사례도 Zend Framework와 같은 프로젝트의 "2.0" 시도와 같은 몇 가지 구체적인 사항을 강조합니다. 심포니, 교리, 배.

배경

PHP 5.2에는 Exception이라는 예외 클래스가 하나만 있습니다. Zend Framework/PEAR 개발 표준에 따르면 이 클래스는 라이브러리의 모든 예외 클래스에 대한 기본 클래스입니다. Zend Framework/PEAR 표준에 따라 MyCompany라는 라이브러리를 생성하면 라이브러리의 모든 코드 파일은 MyCompany_로 시작됩니다. 라이브러리에 대한 고유한 예외 기본 클래스인 MyCompany_Exception을 생성하려면 이 클래스를 사용하여 Exception을 상속한 다음 구성 요소(구성 요소)가 이 예외 클래스를 상속하고 발생시킵니다. 예를 들어 MyCompany_Foo 구성 요소가 있는 경우 구성 요소 내에서 사용할 예외 기본 클래스 MyCompany_Foo_Exception을 생성할 수 있습니다. 이러한 예외는 MyCompany_Foo_Exception, MyCompany_Exception 또는 Exception을 포착하는 코드로 포착될 수 있습니다. 이 구성 요소를 사용하는 라이브러리의 다른 코드의 경우 이는 3단계 예외(또는 MyCompany_Foo_Exception의 하위 클래스 수에 따라 그 이상)이며 적절하다고 판단되는 경우 이러한 예외를 처리할 수 있습니다.


PHP5에서는 기본 예외 클래스가 이미 중첩 기능을 지원합니다. 중첩이란 무엇입니까? 중첩은 특정 예외 또는 원래 예외를 참조하여 생성된 새 예외 개체를 포착하는 기능입니다. 이렇게 하면 호출자 속성이 더 많은 공용 유형의 오버헤드 라이브러리에 나타나는 두 예외 클래스에 반영될 수 있으며 물론 원래 예외 동작이 있는 예외 클래스에도 반영됩니다.

이러한 기능이 왜 유용한가요? 다른 코드를 사용하여 고유한 유형의 예외를 발생시키는 것이 가장 효율적인 코드인 경우가 많습니다. 이 코드는 어댑터 패턴을 사용하여 캡슐화된 좀 더 적응 가능한 기능을 제공하는 타사 코드 라이브러리의 코드이거나 일부 PHP 확장을 활용하여 예외를 발생시키는 간단한 코드일 수 있습니다.


예를 들어 Zend_Db 구성 요소에서는 어댑터 패턴을 사용하여 특정 PHP 확장을 캡슐화하여 데이터베이스 추상화 계층을 생성합니다. 어댑터에서 Zend_Db는 PDO를 캡슐화하고 PDO는 Zend_Db가 이러한 PDO를 포착해야 하는 자체 예외 PDOException을 발생시킵니다. 이를 통해 개발자는 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标准库;开发者就可以以同样的方式在他们的项目中,并且复用这些项目的新的最佳的方法已经出现。


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

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

现在在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客户端/服务端能够发送和解释的请求。


第二组是逻辑(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 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. 这为捕捉你的类库组件中的异常提供了极大的便利. 此外, 通过分析异常的类型, 我们也能看出某个异常的含义.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.