0

0

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

php中文网

php中文网

发布时间:2016-06-01 12:30:51

|

1077人浏览过

|

来源于php中文网

原创

本文面向希望了解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。

  处理多个错误

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

  index_php5_2.php

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

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

 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();
 }
}
?>


  这里有两个地方的调用可能导致程序出错(__construct()和getCommandObject())。尽管如此,我们不需要调整我们的客户代码。你可以在try语句中增添众多内容,然后在catch中统一处理。如果CommandManager 对象的构造函数抛出一个异常,则try语句中的执行中止,然后catch语句被调用捕捉相关的异常。同样地,getCommandObject()也是如此。这样,我们有同时存在两个潜在的引发错误的地方,和一个唯一的语句来处理所有的错误。这让我们的代码看起来更加整洁,又可以满足错误处理的要求。和前面提到的PHP的传统的错误方法相比,显然很有优势。

  index_php5_2.php 后半段

  注意:尽管和index_php5.php相比,前半段代码有两个可能出错的地方,这段代码和index_php5.php的后半段完全相同。

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


  还有一个地方我们没有提到。我们怎样区分不同类型的错误?例如,我们可能希望用一种方法来处理找不到目录的错误,而用另一种方法来处理非法的command类。

  Exception类可以接受一个可选的整型的错误标识,这是在catch语句中区分不同错误类型的一个方法。

  index_php5_3.php

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

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

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

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


  通过传递 CMDMAN_ILLEGALCLASS_ERROR和 CMDMAN_GENERAL_ERROR其中之一的参数给我们抛出的异常对象,我们就可以让客户代码区分不同类型的错误,并定义不同的处理策略。

  index_php5_3.php

<?php // PHP 5
try {
 $mgr = new CommandManager();
 $cmd = $mgr->getCommandObject('realcommand');
 $cmd->execute();
} catch (Exception $e) {
 if ($e->getCode() == CommandManager::CMDMAN_GENERAL_ERROR) {
  // no way of recovering
  die($e->getMessage());
 } else if ($e->getCode() == CommandManager::CMDMAN_ILLEGALCLASS_ERROR) {
  error_log($e->getMessage());
  print "attempting recovery\n";
  // perhaps attempt to invoke a default command?
 }
}
?>


  我们也可以用另一种方法来实现这样的效果—从最根本的Exception类中派生出代表不同类型异常的子类,再抛出和捕捉。

  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的异常机制中另一个需要讨论的地方。

  异常的传递、重掷异常

  如果我们已经触发了一些在发生时无法马上处理的异常,有一个很好的解决方案—将处理异常的责任交回给调用当前方法的代码,也就是在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避免了直接返回错误标识,方法的返回值是可以由你的类来决定的。其它程序员使用你的代码时,可以指定返回一个他希望的形式,而不需要令人疲倦的不停地测试。

Riffo
Riffo

Riffo是一个免费的文件智能命名和管理工具

下载

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法
包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法

本专题汇总了包子漫画官网和网页版入口,提供最新章节抢先看方法、正版免费阅读指南,以及稳定访问方式,帮助用户快速直达包子漫画页面,无广告畅享全集漫画内容。

47

2026.02.10

MC.JS网页版快速畅玩指南_MC.JS官网在线入口及免安装体验方法
MC.JS网页版快速畅玩指南_MC.JS官网在线入口及免安装体验方法

本专题汇总了MC.JS官网入口和网页版快速畅玩方法,提供免安装访问、不同版本(1.8.8、1.12.8)在线体验指南,以及正版网页端操作说明,帮助玩家轻松进入MC.JS世界,实现即时畅玩与高效体验。

34

2026.02.10

谷歌邮箱网页版登录与注册全指南_Gmail账号快速访问与安全操作教程
谷歌邮箱网页版登录与注册全指南_Gmail账号快速访问与安全操作教程

本专题汇总了谷歌邮箱网页版的最新登录入口和注册方法,详细提供官方账号快速访问方式、网页版操作教程及安全登录技巧,帮助用户轻松管理Gmail邮箱账户,实现高效、安全的邮箱使用体验。

25

2026.02.10

铁路12306订票与退改全攻略_高效购票与座位选取技巧
铁路12306订票与退改全攻略_高效购票与座位选取技巧

本专题全面汇总铁路12306订票、退票、改签及候补订单操作技巧,提供车厢座位分布参考、抢票攻略和高铁安检注意事项,帮助新手用户快速掌握高效购票与退改流程,提高出行效率和体验。

31

2026.02.10

TensorFlow2深度学习模型实战与优化
TensorFlow2深度学习模型实战与优化

本专题面向 AI 与数据科学开发者,系统讲解 TensorFlow 2 框架下深度学习模型的构建、训练、调优与部署。内容包括神经网络基础、卷积神经网络、循环神经网络、优化算法及模型性能提升技巧。通过实战项目演示,帮助开发者掌握从模型设计到上线的完整流程。

0

2026.02.10

Vue3组合式API与组件开发实战
Vue3组合式API与组件开发实战

本专题讲解 Vue 3 组合式 API 的核心概念与应用技巧,深入分析响应式系统、生命周期管理、组件设计与复用策略。通过完整项目案例,指导前端开发者实现高性能、结构清晰的 Vue 应用,提升开发效率与代码可维护性。

4

2026.02.10

Go语言微服务架构与gRPC实战
Go语言微服务架构与gRPC实战

本专题面向有 Go 基础的开发者,系统讲解微服务架构设计与 gRPC 的高效应用。内容涵盖服务拆分、RPC 通信、负载均衡、错误处理、服务注册与发现等关键技术。通过实战案例,帮助开发者搭建高性能、可扩展的 Go 微服务系统。

1

2026.02.10

React 18状态管理与Hooks高级实践
React 18状态管理与Hooks高级实践

本专题专注于 React 18 的高级开发技术,详细讲解 useState、useEffect、useReducer、useContext 等 Hooks 的使用技巧,以及 Redux、Zustand 等状态管理工具的集成与优化方法。通过真实案例,帮助前端开发者构建可维护、性能优良的现代 React 应用。

4

2026.02.10

Node.js后端开发与Express框架实践
Node.js后端开发与Express框架实践

本专题针对初中级 Node.js 开发者,系统讲解如何使用 Express 框架搭建高性能后端服务。内容包括路由设计、中间件开发、数据库集成、API 安全与异常处理,以及 RESTful API 的设计与优化。通过实际项目演示,帮助开发者快速掌握 Node.js 后端开发流程。

2

2026.02.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Git 教程
Git 教程

共21课时 | 3.5万人学习

麻省理工大佬Python课程
麻省理工大佬Python课程

共34课时 | 5.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号