在 PHP 开发中,不少程序员都遇到过 try-catch 结构未能如预期那样捕获异常的情况。本文将探讨一个具体实例,帮助你理解 PHP 命名空间是如何影响异常捕获的,特别是涉及 mysqli 数据库操作时的异常处理。
问题描述
假设有一段 PHP 代码,尝试向数据库插入一条记录,由于存在重复键值 “dt”,触发了 “Duplicate entry ‘dt’ for key ‘username_UNIQUE’” 的异常。此异常在多个 try-catch 层级中未能被捕获,直至到达 index.php
文件的顶层 catch 块才被拦截。代码的大致结构是这样的:
- index.php – 包含一个全局的 try-catch 结构,并调用
$app->go()
。 - App 类 – 在
go()
方法中有另一个 try-catch 结构,用于调用处理请求的processRequest()
方法,后者执行了 mysqli 的插入操作。
示例代码:
// index.php
try {
$app = $factory->getClass('app\app');
echo $app->go();
} catch (Exception $e) {
die("Captured in index.php: " . $e->getMessage());
}
// App类
namespace app;
class App {
public function go() {
try {
$result = $this->requestHandler->processRequest(false);
} catch (Exception $e) {
die("Caught in App::go(): " . $e->__toString());
}
return $result;
}
}
为什么 mysqli
触发的异常没有在 App::go()
方法中的 catch 块被捕获,而是直接跳转到了 index.php
的全局 catch 块?
原因分析:PHP 的命名空间规则
当 App
类位于 app
命名空间内时,catch (Exception $e)
语句默认只捕获该命名空间下的 Exception
异常,而不是全局命名空间 \Exception
下的异常。因此,当 mysqli
抛出的是 \Exception
类型的异常时,App
类中的 catch 无法识别并处理这些异常,导致它们继续向上抛,直到被 index.php
中的 catch 捕获。
要解决这个问题,只需在 App
类的 catch 语句中明确指出要捕获全局命名空间下的 \Exception
。修改后的代码如下:
// 修改后的App类
namespace app;
class App {
public function go() {
try {
$result = $this->requestHandler->processRequest(false);
} catch (\Exception $e) { // 指定捕获全局命名空间的异常
die("Caught in App::go(): " . $e->__toString());
}
return $result;
}
}
通过上述调整,App::go()
方法就能够成功捕获由 mysqli
操作引发的异常,而不需要依赖于 index.php
文件中的顶层异常处理逻辑。