除非您打算做一些有意义的事情,否则您不应捕获异常。
“有意义的事情”可能是其中之一:
处理异常
最明显的有意义的动作是处理异常,例如通过显示错误消息并中止操作:
try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
echo "Error while connecting to database!";
die;
}
记录或部分清除
有时,您不知道如何在特定上下文中正确处理异常。也许您缺少有关“全局”的信息,但是您确实想将故障记录在尽可能接近发生故障的位置。在这种情况下,您可能想要捕获,记录并重新抛出:
try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
logException($e); // does something
throw $e;
}
一个相关的场景是,您可以在正确的位置对失败的操作执行一些清理,但又不能决定应该如何在顶级处理失败。在早期的PHP版本中,这将实现为
$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
$connect->insertSomeRecord();
}
catch (Exception $e) {
$connect->disconnect(); // we don't want to keep the connection open anymore
throw $e; // but we also don't know how to respond to the failure
}
PHP 5.5引入了finally
关键字,因此对于清理方案,现在有另一种方法可以解决此问题。如果清除代码无论发生了什么(无论是错误还是成功)都需要运行,则现在可以执行此操作,同时透明地允许传播所有抛出的异常:
$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
$connect->insertSomeRecord();
}
finally {
$connect->disconnect(); // no matter what
}
错误抽象(带有异常链接)
第三种情况是您希望在逻辑上将许多可能的故障归为一个整体。逻辑分组的示例:
class ComponentInitException extends Exception {
// public constructors etc as in Exception
}
class Component {
public function __construct() {
try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
}
}
}
在这种情况下,您不希望用户Component
知道它是使用数据库连接实现的(也许您希望将来保持打开状态并使用基于文件的存储)。因此,您的规范Component
将说“在初始化失败的情况下,ComponentInitException
将引发”。这使的使用者Component
可以捕获预期类型的异常,同时还允许调试代码访问所有(与实现有关的)详细信息。
提供更丰富的上下文(带有异常链接)
最后,在某些情况下,您可能希望为该异常提供更多上下文。在这种情况下,将异常包装在另一个异常中是有意义的,该异常包含有关发生错误时您要执行的操作的更多信息。例如:
class FileOperation {
public static function copyFiles() {
try {
$copier = new FileCopier(); // the constructor may throw
// this may throw if the files do no not exist
$copier->ensureSourceFilesExist();
// this may throw if the directory cannot be created
$copier->createTargetDirectory();
// this may throw if copying a file fails
$copier->performCopy();
}
catch (Exception $e) {
throw new Exception("Could not perform copy operation.", 0, $e);
}
}
}
这种情况与上面的情况类似(示例可能不是最好的情况),但是它说明了提供更多上下文的意义:如果引发异常,则表明文件复制失败。但是为什么失败了?包装的异常中提供了此信息(如果示例复杂得多,则异常可以存在多个级别)。
如果考虑以下情况,则说明执行此操作的价值:例如,创建一个UserProfile
对象会导致文件被复制,因为用户配置文件存储在文件中,并且支持事务语义:您可以“撤消”更改,因为它们仅在以下情况下执行:配置文件的副本,直到您提交。
在这种情况下,如果您做了
try {
$profile = UserProfile::getInstance();
}
并且导致捕获到“无法创建目标目录”异常错误,您将有权利感到困惑。在提供上下文的其他异常层中包装此“核心”异常将使错误更易于处理(“创建配置文件复制失败”->“文件复制操作失败”->“无法创建目标目录”)。