PHP 5.0异常处理机制深度探索

转载 2007年06月16日 18:36:00
本文面向希望了解PHP5异常处理机制的程序员。阅读本文你需要具有一定面向对象编程和PHP基础。

  PHP5内建的异常类需要有以下成员方法:

__construct() 构造函数,需要一个出错信息和一个可选的整型错误标记作参数
getMessage() 取得出错信息
getCode()
出错的代码
getFile() 异常发生的文件
getLine() 异常发生的行数
getTrace() 跟踪异常每一步传递的路线,存入数组,返回该数组
getTraceAsString() 和getTrace()功能一样,但可以将数组中的元素转成字符串并按一定格式输出

  可以看出来,Exception 类的结构和Pear_Error 很相似。当你的脚本中遇到一个错误,你可以建立你的异常对象:

$ex = new Exception( "Could not open $this->file" );

  Exception类的构造函数将接受一个出错信息和一个错误代码。

  使用 throw关键字

  建立一个Exception对象后你可以将对象返回,但不应该这样使用,更好的方法是用throw关键字来代替。throw用来抛出异常:

throw new Exception( "my message", 44 );

  throw 将脚本的执行中止,并使相关的Exception对象对客户代码可用。

  以下是改进过的getCommandObject() 方法:

  index_php5.php

<?php // PHP 5
require_once('cmd_php5/Command.php');
class CommandManager {
 private $cmdDir = "cmd_php5";

 function getCommandObject($cmd) {
  $path = "{$this->cmdDir}/{$cmd}.php";
  if (!file_exists($path)) {
   throw new Exception("Cannot find $path");
  }
 require_once $path;
 if (!class_exists($cmd)) {
  throw new Exception("class $cmd does not exist");
 }

 $class = new ReflectionClass($cmd);
 if (!$class->isSubclassOf(new ReflectionClass('Command'))) {
  throw new Exception("$cmd is not a Command");
 }
 return new $cmd();
}
}
?>

  代码中我们使用了PHP5的反射(Reflection)API来判断所给的类是否是属于Command 类型。在错误的路径下执行本脚本将会报出这样的错误:

Fatal error: Uncaught exception 'Exception' with message 'Cannot find command/xrealcommand.php' in /home/xyz/BasicException.php:10
Stack trace:
#0 /home/xyz/BasicException.php(26):
CommandManager->getCommandObject('xrealcommand')
#1 {main}
thrown in /home/xyz/BasicException.php on line 10

  默认地,抛出异常导致一个fatal error。这意味着使用异常的类内建有安全机制。而仅仅使用一个错误标记,不能拥有这样的功能。处理错误标记失败只会你的脚本使用错误的值来继续执行。

  Try-catch 语句

  为了进一步处理异常,我们需要使用try-catch语句—包括Try语句和至少一个的catch语句。任何调用 可能抛出异常的方法的代码都应该使用try语句。Catch语句用来处理可能抛出的异常。以下显示了我们处理getCommandObject()抛出的异常的方法:

  index_php5.php 后半段

<?php
// PHP 5
try {
 $mgr = new CommandManager();
 $cmd = $mgr->getCommandObject('realcommand');
 $cmd->execute();
} catch (Exception $e) {
 print $e->getMessage();
 exit();
}
?>

  可以看到,通过结合使用throw关键字和try-catch语句,我们可以避免错误标记“污染”类方法返回的值。因为“异常”本身就是一种与其它任何对象不同的PHP内建的类型,不会产生混淆。

  如果抛出了一个异常,try语句中的脚本将会停止执行,然后马上转向执行catch语句中的脚本。

  如果异常抛出了却没有被捕捉到,就会产生一个fatal error。 Exception类的子类

  有两个理由让我们想要从Exception类中派生中子类:

  1. 让子类提供自定义的功能;

  2. 区分不同类型的异常;

  看第二个例子。使用CommandManager类时我们可能会产生两个错误:一个是一般性的错误如找不到目录,另一个是找不到或无法生成Command对象。这样我们需要针对这两个错误来定义两种异常子类型。

  index_php5_4.php

<?php
// PHP 5
require_once('cmd_php5/Command.php');
class CommandManagerException extends Exception{}
class IllegalCommandException extends Exception{}

class CommandManager {
 private $cmdDir = "cmd_php5";

 function __construct() {
  if (!is_dir($this->cmdDir)) {
   throw new CommandManagerException("directory error: $this->cmdDir");
  }
 }

 function getCommandObject($cmd) {
  $path = "{$this->cmdDir}/{$cmd}.php";
  if (!file_exists($path)) {
   throw new IllegalCommandException("Cannot find $path");
  }
  require_once $path;
  if (!class_exists($cmd)) {
   throw new IllegalCommandException("class $cmd does not exist");
  }

  $class = new ReflectionClass($cmd);
  if (!$class->isSubclassOf(new ReflectionClass('Command'))) {
   throw new IllegalCommandException("$cmd is not a Command");
  }
  return $class->newInstance();
 }
}
?>

  当我们的类不能找到正确的command目录时,将抛出一个CommandManagerException异常;当在生成Command对象时产生错误,则getCommandObject()方法将抛出一个IllegalCommandException异常。注意存在多个可能导致抛出IllegalCommandException异常的原因(如未找到文件,或在文件中未找到正确的类)。我们将前两个例子结合起来并为IllegalCommandException提供整型的错误标识常量来代表不同类型的出错原因。

  现在CommandManager类已经具备了处理这多种出错情况的能力,我们可以增加新的catch语句来匹配不同的错误类型。

  index_php5_4.php 后半段

<?php // PHP 5
try {
 $mgr = new CommandManager();
 $cmd = $mgr->getCommandObject('realcommand');
 $cmd->execute();
} catch (CommandManagerException $e) {
 die($e->getMessage());
} catch (IllegalCommandException $e) {
 error_log($e->getMessage());
 print "attempting recovery/n";
 // perhaps attempt to invoke a default command?
} catch (Exception $e) {
 print "Unexpected exception/n";
 die($e->getMessage());
}
?>

  如果CommandManager 对象抛出一个CommandManagerException异常,则相对应的catch语句将会执行。每个catch语句的参数就像是一个匹配测试一样,第一个发生匹配的catch语句将会执行,而不执行其它的catch语句。所以,你应当将针对特定异常的catch语句写在前面,而将针对一般性的异常的catch语句写在后面。

  如果你将catch语句这样写:

<?php
// PHP 5
try {
 $mgr = new CommandManager();
 $cmd = $mgr->getCommandObject('realcommand');
 $cmd->execute();
} catch (Exception $e) {
 print "Unexpected exception/n";
 die($e->getMessage());
} catch (CommandManagerException $e) {
 die($e->getMessage());
} catch (IllegalCommandException $e) {
 error_log($e->getMessage());
 print "attempting recovery/n";
 // perhaps attempt to invoke a default command?
}
?>

  那么当异常抛出时,不管是什么异常第一个catch语句catch (Exception $e){}将总是被执行。这是由于任何异常都从属于Exception类型,所以总是匹配。这就达不到我们所要的针对特定异常进行不同处理的目的。

  如果你在捕捉特定类型的异常,那么在最后一个catch语句中捕捉Exception类型的异常是一个好主意。最后一个catch语句表示catch-all,捕捉所有异常。当然,你可能不想马上处理异常,而是想要将它传递,然后在适当的时候处理。这是PHP的异常机制中另一个需要讨论的地方。

###adv###  异常的传递、重掷异常

  如果我们已经触发了一些在发生时无法马上处理的异常,有一个很好的解决方案—将处理异常的责任交回给调用当前方法的代码,也就是在catch语句中再次抛出异常(重掷异常)。这将使异常沿着方法的调用链向上传递。

  index_php5_5.php

<?php
// PHP 5
class RequestHelper {
 private $request = array();
 private $defaultcmd = 'defaultcmd';
 private $cmdstr;

 function __construct($request_array=null) {
  if (!is_array($this->request = $request_array)) {
   $this->request=$_REQUEST;
  }
 }

 function getCommandString() {
  return ($this->cmdstr ? $this->cmdstr : ($this->cmdstr=$this->request['cmd']));
 }

 function runCommand() {
  $cmdstr = $this->getCommandString();
  try {
   $mgr = new CommandManager();
   $cmd = $mgr->getCommandObject($cmdstr);
   $cmd->execute();
  } catch (IllegalCommandException $e) {
   error_log($e->getMessage());
   if ($cmdstr != $this->defaultcmd) {
    $this->cmdstr = $this->defaultcmd;
    $this->runCommand();
   } else {
    throw $e;
   }
  } catch (Exception $e) {
   throw $e;
  }
 }
}

$helper = new RequestHelper(array(cmd=>'realcommand'));
$helper->runCommand();
?>

  以上我们使用了RequestHelper类中的一段客户代码。RequestHelper用来处理用户提供的请求数据。在构造函数中我们接受一个用来debug的数组。如果没有接受到这个数组,类将使用$_REQUEST数组。无论哪个数组被使用,它都将分配给名为$request的变量。客户代码通过给出一个request数组的cmd元素,告知它想要执行的command。getCommandString()方法则测试一个名为$cmdstr的属性。如果它是空的,则方法将$request中的cmd元素的内容分配给$cmdstr,并返回值。如果不是空的,方法直接返回$cmdstr属性的值。通过这样的机制,command字符串可以在RequestHelper类中被覆写。

  在最后我们将除IllegalCommandException外的所有异常对象都将交给更高一级的类来延后处理。我们在最后一个catch语句中再次抛出异常。

} catch (Exception $e) {
 throw $e;
}

  如果我们捕捉到一个IllegalCommandException 异常,我们首先尝试去调用 一个默认的command。我们通过将$cmdstr属性设置为与$defaultcmd等值,并重复地调用runCommand方法。如果$cmdstr和$defaultcmd字符串已经相等,我们没有什么需要做的,则重掷异常。

  事实上在 Zend引擎II将会自动重掷所有未匹配的异常,所以我们可以省略最后一个catch语句。这是CommandManager::getCommandObject()的最后一行:

return $class->newInstance();

  这里要注意两个问题:

  首先,我们假设CommandManager类的构造函数不需要参数。在本文中我们不讨论需要参数的情况。

  其次,我们假设command类(这里是指我们自定义的realcommand)可以被实例化。如果构造函数被声明为private,这个语句将抛出一个ReflectionException对象。如果我们没有在RequestHelper中处理异常,则这个异常将被传递到调用RequestHelper的代码中。如果一个异常被隐性地抛出,你最好在文档中说明一下,或者手动地抛出这个异常--这样其他的程序员使用你的代码时容易处理可能发生的异常情况。

  获得异常相关的更多信息

  以下是用来格式化输出异常信息的代码:

  index_php5_6.php

<?php
// PHP 5
class Front {
 static function main() {
  try {
   $helper = new RequestHelper(array(cmd=>'realcommand'));
   $helper->runCommand();
  } catch (Exception $e) {
   print "<h1>".get_class($e)."</h1>/n";
   print "<h2>{$e->getMessage()}
  ({$e->getCode()})</h2>/n/n";
  print "file: {$e->getFile()}<br />/n";
  print "line: {$e->getLine()}<br />/n";
  print $e->getTraceAsString();
  die;
 }
}
}
Front::main();
?>

  如果你的realcommand类无法被实例化(例如你将它的构造函数声明为private)并运行以上代码,你可以得到这样的输出:

ReflectionException Access to non-public constructor of class realcommand (0)
file: c:/MyWEB/Apache/htdocs/php5exception/index_php5_4.php
line: 31
#0 c:/MyWEB/Apache/htdocs/php5exception/index_php5_5.php(25): CommandManager->getCommandObject()

#1 c:/MyWEB/Apache/htdocs/php5exception/index_php5_6.php(10): RequestHelper->runCommand('realcommand')

#2 c:/MyWEB/Apache/htdocs/php5exception/index_php5_6.php(23): Front::main()

#3 {main}

  你可以看到getFile()和getLine()分别返回发生异常的文件和行数。GetStackAsString()方法返回每一层导致异常发生的方法调用的细节。从#0一直到#4,我们可以清楚地看到异常传递的路线。

  你也可以使用getTrace()方法来得到这些信息,getTrace()返回一个多维数组。第一个元素包含有异常发生的位置,第二个元素包含外部方法调用的细节,直到最高一层的调用。这个数组的每个元素本身也是一个数组,包含有以下几个键名(key):

key 含义
file 产生异常的文件
line 产生异常的类方法所在行数
function 产生异常的函数/方法
class 调用的方法所在类
type 调用类型:'::' 表示调用静态类成员
'->' 表示实例化调用(先实例化生成对象再调用)
args 类方法接受的参数


  总结

  异常机制提供了几个非常关键的好处:

  (1) 通过将错误处理集中于catch语句中,你可以将错误处理从应用流程中独立出来。这也使代码的可读性提高,看起来令人愉快。我通常采取非常严格的策略来捕捉所有异常并中止脚本执行。这样可以获得所需的附加的弹性,同时实现安全易用的异常管理。

  (2) 重掷异常,将异常数据流从低层传递至高层,就是说异常被传回最适合决定如何处理异常的地方。这看起来会显得有点奇怪,但实际情况中很经常我们在异常发生的时候无法立刻决定如何处理它。

  (3) 异常机制提供的Throw/catch避免了直接返回错误标识,方法的返回值是可以由你的类来决定的。其它程序员使用你的代码时,可以指定返回一个他希望的形式,而不需要令人疲倦的不停地测试。     

电子书下载:DELPHI深度探索(第二版)

下载:http://www.400gb.com/file/54803782
  • MaxWoods
  • MaxWoods
  • 2014年01月18日 16:19
  • 2702

《深度探索c++对象模型》学习笔记

1、c++的布局和存取时间成本?封装并未给c++带来任何的空间或执行期的不良后果,c++在布局和存取时间上的主要额外负担由虚拟化引起。包括: 1)virtual function机制。用以支持一个有...
  • will130
  • will130
  • 2016年05月31日 15:48
  • 282

《Android深度探索(卷1):HAL与驱动开发》实验环境配置与实验资源下载

由于光盘中带的Ubuntu Linux12.04虚拟环境需要CPU支持虚拟化,所有可能有一部分PC无法成功打开虚拟化环境,AMD的CPU也不可以。不过这不要紧,光盘中包含了源代码(sources目录)...
  • nokiaguy
  • nokiaguy
  • 2013年02月24日 20:16
  • 3054

《Android深度探索(卷1):HAL与驱动开发》新书发布

《 Android深度探索(卷1):HAL与驱动开发》分为4篇,分别从搭建开发环境,Linux驱动和Android HAL的基础知识,开发Linux驱动的高级技术和分析典型的Linux驱动源代码4个方...
  • nokiaguy
  • nokiaguy
  • 2013年01月24日 23:17
  • 5719

PHP 5.0异常处理机制(2)

处理多个错误   在目前为止异常处理看起来和我们传统的作法—检验返回的错误标识或对象的值没有什么太大区别。让我们将CommandManager处理地更谨慎,并在构造函数中检查command目录是否存在...
  • louis810523
  • louis810523
  • 2006年09月15日 15:49
  • 662

PHP 5.0异常处理机制(1)

PHP5内建的异常类需要有以下成员方法: __construct() 构造函数,需要一个出错信息和一个可选的整型错误标记作参数 getMessage() 取得出错信息 getCode() 出错的代码 ...
  • louis810523
  • louis810523
  • 2006年09月15日 15:45
  • 715

Android深度探索(卷2)系统应用源代码分析与ROM定制 读书笔记1

1.在config文件中如果加上CONFIG_IKCONFIG_PROC=y,编译下载开机后就会在/proc下面生成config.gz的文件,这个文件可以直接解压使用。 2.Android源码下面就有...
  • m0_37321987
  • m0_37321987
  • 2017年12月22日 17:12
  • 354

利用深度优先搜索算法和广度优先搜索算法解迷宫问题

本来应该是在大一就该掌握的内容,我却迟迟没有去了解,最近比较闲,今天就好好讨论一下图搜索算法的两个分支,深度优先算法与广度优先算法。本篇博客将分别利用深度优先算法和广度优先算法解决迷宫问题。   所...
  • believexfr
  • believexfr
  • 2017年10月22日 14:35
  • 135

深度探索c++对象模型(一)_关于对象

原博客地址:http://www.roading.org//develop/cpp/c对象面面观.html 学习C++应该看过不少关于C与C++的口水贴,以及关于各种对比C与C++效率比较的...
  • A_IIIIIIIII_A
  • A_IIIIIIIII_A
  • 2016年01月04日 11:31
  • 573

算法基础(六):广度优先探索迷宫路径(队列实现!)—超详细版!

算法基础(六):广度优先探索迷宫路径(队列实现!)—超详细版!
  • huiguimoyu
  • huiguimoyu
  • 2014年03月04日 18:44
  • 2926
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:PHP 5.0异常处理机制深度探索
举报原因:
原因补充:

(最多只允许输入30个字)