搜索
首页后端开发php教程PHP 5.0异常处理机制深度探索_PHP

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

Jun 01, 2016 pm 12:30 PM
cmd处理异常我们探索机制深度

本文面向希望了解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避免了直接返回错误标识,方法的返回值是可以由你的类来决定的。其它程序员使用你的代码时,可以指定返回一个他希望的形式,而不需要令人疲倦的不停地测试。

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
PHP类型提示如何起作用,包括标量类型,返回类型,联合类型和无效类型?PHP类型提示如何起作用,包括标量类型,返回类型,联合类型和无效类型?Apr 17, 2025 am 12:25 AM

PHP类型提示提升代码质量和可读性。1)标量类型提示:自PHP7.0起,允许在函数参数中指定基本数据类型,如int、float等。2)返回类型提示:确保函数返回值类型的一致性。3)联合类型提示:自PHP8.0起,允许在函数参数或返回值中指定多个类型。4)可空类型提示:允许包含null值,处理可能返回空值的函数。

PHP如何处理对象克隆(克隆关键字)和__clone魔法方法?PHP如何处理对象克隆(克隆关键字)和__clone魔法方法?Apr 17, 2025 am 12:24 AM

PHP中使用clone关键字创建对象副本,并通过\_\_clone魔法方法定制克隆行为。1.使用clone关键字进行浅拷贝,克隆对象的属性但不克隆对象属性内的对象。2.通过\_\_clone方法可以深拷贝嵌套对象,避免浅拷贝问题。3.注意避免克隆中的循环引用和性能问题,优化克隆操作以提高效率。

PHP与Python:用例和应用程序PHP与Python:用例和应用程序Apr 17, 2025 am 12:23 AM

PHP适用于Web开发和内容管理系统,Python适合数据科学、机器学习和自动化脚本。1.PHP在构建快速、可扩展的网站和应用程序方面表现出色,常用于WordPress等CMS。2.Python在数据科学和机器学习领域表现卓越,拥有丰富的库如NumPy和TensorFlow。

描述不同的HTTP缓存标头(例如,Cache-Control,ETAG,最后修饰)。描述不同的HTTP缓存标头(例如,Cache-Control,ETAG,最后修饰)。Apr 17, 2025 am 12:22 AM

HTTP缓存头的关键玩家包括Cache-Control、ETag和Last-Modified。1.Cache-Control用于控制缓存策略,示例:Cache-Control:max-age=3600,public。2.ETag通过唯一标识符验证资源变化,示例:ETag:"686897696a7c876b7e"。3.Last-Modified指示资源最后修改时间,示例:Last-Modified:Wed,21Oct201507:28:00GMT。

说明PHP中的安全密码散列(例如,password_hash,password_verify)。为什么不使用MD5或SHA1?说明PHP中的安全密码散列(例如,password_hash,password_verify)。为什么不使用MD5或SHA1?Apr 17, 2025 am 12:06 AM

在PHP中,应使用password_hash和password_verify函数实现安全的密码哈希处理,不应使用MD5或SHA1。1)password_hash生成包含盐值的哈希,增强安全性。2)password_verify验证密码,通过比较哈希值确保安全。3)MD5和SHA1易受攻击且缺乏盐值,不适合现代密码安全。

PHP:服务器端脚本语言的简介PHP:服务器端脚本语言的简介Apr 16, 2025 am 12:18 AM

PHP是一种服务器端脚本语言,用于动态网页开发和服务器端应用程序。1.PHP是一种解释型语言,无需编译,适合快速开发。2.PHP代码嵌入HTML中,易于网页开发。3.PHP处理服务器端逻辑,生成HTML输出,支持用户交互和数据处理。4.PHP可与数据库交互,处理表单提交,执行服务器端任务。

PHP和网络:探索其长期影响PHP和网络:探索其长期影响Apr 16, 2025 am 12:17 AM

PHP在过去几十年中塑造了网络,并将继续在Web开发中扮演重要角色。1)PHP起源于1994年,因其易用性和与MySQL的无缝集成成为开发者首选。2)其核心功能包括生成动态内容和与数据库的集成,使得网站能够实时更新和个性化展示。3)PHP的广泛应用和生态系统推动了其长期影响,但也面临版本更新和安全性挑战。4)近年来的性能改进,如PHP7的发布,使其能与现代语言竞争。5)未来,PHP需应对容器化、微服务等新挑战,但其灵活性和活跃社区使其具备适应能力。

为什么要使用PHP?解释的优点和好处为什么要使用PHP?解释的优点和好处Apr 16, 2025 am 12:16 AM

PHP的核心优势包括易于学习、强大的web开发支持、丰富的库和框架、高性能和可扩展性、跨平台兼容性以及成本效益高。1)易于学习和使用,适合初学者;2)与web服务器集成好,支持多种数据库;3)拥有如Laravel等强大框架;4)通过优化可实现高性能;5)支持多种操作系统;6)开源,降低开发成本。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境