PHP 5.0异常处理机制深度探索3
2006-11-28 23:23:00
Exception类的子类 有两个理由让我们想要从Exception类中派生中子类: 1. 让子类提供自定义的功能; 2. 区分不同类型的异常; 看第二个例子。使用CommandManager类时我们可能会产生两个错误:一个是一般性的错误如找不到目录,另一个是找不到或无法生成Command对象。这样我们需要针对这两个错误来定义两种异常子类型。 index_php5_4.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 后半段 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 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 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语句中再次抛出异常。 throw $e; } 如果我们捕捉到一个IllegalCommandException 异常,我们首先尝试去调用 一个默认的command。我们通过将$cmdstr属性设置为与$defaultcmd等值,并重复地调用runCommand方法。如果$cmdstr和$defaultcmd字符串已经相等,我们没有什么需要做的,则重掷异常。 事实上在 Zend引擎II将会自动重掷所有未匹配的异常,所以我们可以省略最后一个catch语句。这是CommandManager::getCommandObject()的最后一行: 这里要注意两个问题: 首先,我们假设CommandManager类的构造函数不需要参数。在本文中我们不讨论需要参数的情况。 其次,我们假设command类(这里是指我们自定义的realcommand)可以被实例化。如果构造函数被声明为private,这个语句将抛出一个ReflectionException对象。如果我们没有在RequestHelper中处理异常,则这个异常将被传递到调用RequestHelper的代码中。如果一个异常被隐性地抛出,你最好在文档中说明一下,或者手动地抛出这个异常--这样其他的程序员使用你的代码时容易处理可能发生的异常情况。 获得异常相关的更多信息 以下是用来格式化输出异常信息的代码: index_php5_6.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)并运行以上代码,你可以得到这样的输出: 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避免了直接返回错误标识,方法的返回值是可以由你的类来决定的。其它程序员使用你的代码时,可以指定返回一个他希望的形式,而不需要令人疲倦的不停地测试。 |


ccna30
博客统计信息
热门文章
最新评论
友情链接