PHP脚本异常处理的实用技巧

错误和异常不是一回事

在写 PHP 脚本时,很多人把“错误”和“异常”混着用,其实它们是两码事。比如访问一个不存在的数组键,会触发 Notice;除以零可能抛出 Warning。这些属于“错误”,而“异常”是通过 throw 手动抛出来的,需要用 try-catch 捕获。

举个例子:你写了个脚本去读配置文件,结果文件被误删了。这时候 fopen 失败不会自动抛异常,而是返回 false 并打个 Warning。如果你没检查返回值,程序就可能带着脏数据往下跑,最后输出错得离谱的结果。

用 try-catch 捕住关键操作

数据库操作、远程 API 调用、文件读写这些容易出问题的地方,一定要包在 try-catch 里。虽然 PHP 内部函数大多不主动抛异常,但你可以自己封装。

比如用 PDO 连数据库时,开启异常模式更省心:

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

这样一旦执行 SQL 出错,就会抛出 PDOException,能被 catch 到。

自定义异常处理让日志更清晰

系统默认的报错方式太粗暴,页面直接崩给用户看,不合适。我们可以在脚本开头注册自己的处理器:

set_exception_handler(function($exception) {
    error_log("[EXCEPTION] " . $exception->getMessage() .
        " in " . $exception->getFile() .
        " on line " . $exception->getLine());
    echo "系统正忙,请稍后再试。";
});

这样即使漏掉某个 try-catch,也不会暴露敏感路径或代码结构。

别让小问题拖垮整个脚本

有个定时任务脚本,每天处理几百个用户数据。某天其中一个用户的数据格式异常,导致脚本直接终止,后面的全没处理。后来加了异常包裹:

foreach ($users as $user) {
    try {
        processUser($user);
    } catch (Exception $e) {
        error_log("处理用户 {$user['id']} 失败:" . $e->getMessage());
        continue;
    }
}

现在单个用户的错误不会影响整体运行,还能记录下具体哪条出了问题,方便后续修复。

合理使用 finally 做收尾工作

有些资源需要无论成败都释放,比如锁文件、临时连接。finally 就派上用场了。

$fp = fopen('/tmp/lock.txt', 'w');
try {
    if (!flock($fp, LOCK_EX | LOCK_NB)) {
        throw new RuntimeException('无法获取锁');
    }
    // 执行关键操作
    doSomething();
} catch (Exception $e) {
    error_log($e->getMessage());
    throw $e; // 重新抛出
} finally {
    fclose($fp); // 保证文件句柄关闭
}

这样即使中间出错,锁文件也能正常释放,避免后续任务被卡住。