try / catch / finally
概述
try/catch/finally 是 PHP 异常处理的核心语法结构。try 块包含可能抛出异常的代码,catch 块捕获并处理特定类型的异常,finally 块无论是否发生异常都会执行,通常用于资源清理。
PHP 7.0+ 中 catch 支持同时捕获多种异常类型(用 | 分隔),PHP 8.0+ 中异常处理的性能和语法进一步优化。
版本说明
- PHP 5.0 引入 try/catch
- PHP 5.5 引入 finally
- PHP 7.1 支持多异常类型捕获(
catch (A | B $e)) - PHP 8.0+ 异常不再自动转换为 Error
基础概念
基本结构
php
<?php
declare(strict_types=1);
try {
// 可能抛出异常的代码
$result = riskyOperation();
} catch (SpecificException $e) {
// 处理特定异常
} catch (Throwable $e) {
// 兜底处理所有异常
} finally {
// 无论是否异常都执行
// 用于资源释放、日志记录等
}各部分说明
| 关键字 | 作用 | 是否必需 |
|---|---|---|
try | 包含可能抛出异常的代码 | 必需 |
catch | 捕获并处理异常 | 至少一个(或 finally) |
finally | 始终执行的清理代码 | 可选 |
语法与代码
基本 try/catch
php
<?php
declare(strict_types=1);
function divide(int $a, int $b): float
{
if ($b === 0) {
throw new InvalidArgumentException('除数不能为零');
}
return $a / $b;
}
try {
echo divide(10, 2); // 5
echo divide(10, 0); // 抛出异常
echo '这行不会执行'; // 异常后不会执行
} catch (InvalidArgumentException $e) {
echo "捕获到异常: " . $e->getMessage(); // 捕获到异常: 除数不能为零
}多重 catch
php
<?php
declare(strict_types=1);
function processFile(string $path): string
{
if (!file_exists($path)) {
throw new FileNotFoundException("文件不存在: {$path}");
}
if (!is_readable($path)) {
throw new RuntimeException("文件不可读: {$path}");
}
return file_get_contents($path);
}
try {
$content = processFile('/some/file.txt');
} catch (FileNotFoundException $e) {
echo "文件未找到: " . $e->getMessage();
} catch (RuntimeException $e) {
echo "运行时错误: " . $e->getMessage();
} catch (Throwable $e) {
echo "未知错误: " . $e->getMessage();
}finally 始终执行
php
<?php
declare(strict_types=1);
function readFileContent(string $path): ?string
{
$handle = null;
try {
$handle = fopen($path, 'r');
if ($handle === false) {
throw new RuntimeException("无法打开文件");
}
return stream_get_contents($handle);
} catch (RuntimeException $e) {
echo "错误: " . $e->getMessage();
return null;
} finally {
// 无论是否发生异常都会执行
if ($handle !== null) {
fclose($handle);
}
echo "资源已释放\n";
}
}详细说明
finally 的执行时机
php
<?php
declare(strict_types=1);
// 情况 1:无异常 — finally 在 try 后执行
try {
echo "try\n";
} catch (Exception $e) {
echo "catch\n";
} finally {
echo "finally\n"; // 始终执行
}
// 输出: try → finally
// 情况 2:有异常被捕获
try {
echo "try\n";
throw new Exception('error');
} catch (Exception $e) {
echo "catch\n";
} finally {
echo "finally\n";
}
// 输出: try → catch → finally
// 情况 3:异常未被捕获 — finally 执行后异常继续传播
function testUncaught(): void
{
try {
echo "try\n";
throw new RuntimeException('uncaught');
} finally {
echo "finally\n"; // 仍会执行
}
echo "这行不会执行\n";
}
try {
testUncaught();
} catch (RuntimeException $e) {
echo "outer catch\n";
}
// 输出: try → finally → outer catch
// 情况 4:try 中有 return
function testReturn(): string
{
try {
return "try return";
} finally {
echo "finally (return 后仍执行)\n";
}
}
echo testReturn();
// 输出: finally (return 后仍执行) → try returnfinally 与 return/throw
finally 块会在 try/catch 块中的 return、throw、break 语句之前执行。finally 中的 return 会覆盖 try 中的 return。
嵌套 try/catch
php
<?php
declare(strict_types=1);
function outerOperation(): void
{
try {
echo "外层 try\n";
innerOperation();
echo "外层 try 正常完成\n";
} catch (RuntimeException $e) {
echo "外层捕获: " . $e->getMessage() . "\n";
}
}
function innerOperation(): void
{
try {
echo "内层 try\n";
throw new InvalidArgumentException('内层错误');
} catch (InvalidArgumentException $e) {
echo "内层捕获: " . $e->getMessage() . "\n";
// 可以选择重新抛出
throw new RuntimeException('包装后的错误', 0, $e);
}
}
outerOperation();
// 外层 try
// 内层 try
// 内层捕获: 内层错误
// 外层捕获: 包装后的错误异常在循环中的处理
php
<?php
declare(strict_types=1);
$urls = [
'https://example.com/api/1',
'https://example.com/api/2',
'https://invalid-url',
'https://example.com/api/4',
];
$results = [];
foreach ($urls as $url) {
try {
// 模拟 HTTP 请求
if (!str_starts_with($url, 'https://')) {
throw new RuntimeException("无效的 URL: {$url}");
}
$results[$url] = ['status' => 'success'];
} catch (RuntimeException $e) {
$results[$url] = ['status' => 'error', 'message' => $e->getMessage()];
// continue 到下一个 URL
}
}
print_r($results);
// 3 个成功,1 个失败(循环继续)异常信息获取
php
<?php
declare(strict_types=1);
function demonstrateExceptionInfo(): void
{
try {
throw new RuntimeException('演示异常信息', 1001);
} catch (RuntimeException $e) {
echo "类名: " . get_class($e) . "\n";
echo "消息: " . $e->getMessage() . "\n";
echo "错误码: " . $e->getCode() . "\n";
echo "文件: " . $e->getFile() . "\n";
echo "行号: " . $e->getLine() . "\n";
// 堆栈跟踪(数组形式)
$trace = $e->getTrace();
echo "调用深度: " . count($trace) . "\n";
// 堆栈跟踪(字符串形式)
echo "堆栈:\n" . $e->getTraceAsString();
// 前一个异常(异常链)
$previous = $e->getPrevious();
if ($previous !== null) {
echo "前一个异常: " . $previous->getMessage() . "\n";
}
// 字符串表示
echo $e->__toString();
}
}实战示例
数据库事务中的异常处理
php
<?php
declare(strict_types=1);
class DatabaseService
{
public function transferFunds(int $fromId, int $toId, int $amount): void
{
$db = $this->getConnection();
try {
$db->beginTransaction();
$this->debit($db, $fromId, $amount);
$this->credit($db, $toId, $amount);
$db->commit();
} catch (PDOException $e) {
$db->rollBack();
throw new RuntimeException("转账失败: " . $e->getMessage(), 0, $e);
} catch (InvalidArgumentException $e) {
$db->rollBack();
throw $e;
} finally {
// 确保连接关闭
$db = null;
}
}
private function debit(object $db, int $userId, int $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('转账金额必须为正数');
}
// SQL: UPDATE accounts SET balance = balance - ? WHERE id = ?
}
private function credit(object $db, int $userId, int $amount): void
{
// SQL: UPDATE accounts SET balance = balance + ? WHERE id = ?
}
private function getConnection(): object
{
return new stdClass(); // 模拟
}
}注意事项
catch 顺序:从具体到通用
php
<?php
declare(strict_types=1);
// 错误顺序:RuntimeException 在前会捕获所有运行时异常
// try {
// throw new InvalidArgumentException('test');
// } catch (RuntimeException $e) { // 会捕获 InvalidArgumentException
// echo "Runtime";
// } catch (InvalidArgumentException $e) { // 永远不会执行
// echo "Invalid";
// }
// 正确顺序:从子类到父类
try {
throw new InvalidArgumentException('test');
} catch (InvalidArgumentException $e) {
echo "Invalid: " . $e->getMessage(); // 先匹配
} catch (RuntimeException $e) {
echo "Runtime: " . $e->getMessage();
} catch (Throwable $e) {
echo "Throwable";
}finally 中的 return 覆盖问题
php
<?php
declare(strict_types=1);
// finally 中的 return 会覆盖 try/catch 中的 return
function bad(): int
{
try {
return 1;
} finally {
return 2; // 覆盖了 try 的 return!
}
}
echo bad(); // 2(不是 1!)
// 正确做法:不在 finally 中使用 return
function good(): int
{
$result = 0;
try {
$result = 1;
} finally {
// 资源清理,不 return
}
return $result; // 1
}finally 中不要使用 return
finally 块中的 return 语句会覆盖 try 或 catch 块中的 return。这是一个常见的 bug 来源,应避免在 finally 中使用 return。
最佳实践
- catch 顺序从具体到通用:子类异常在前,父类在后
- finally 用于资源释放:文件句柄、数据库连接、锁等
- 异常消息要有意义:包含足够的上下文信息
- 避免空的 catch 块:至少记录日志
- 不要用异常控制流程:异常处理不是普通的控制流
php
<?php
declare(strict_types=1);
// 最佳实践模板
function processOrder(int $orderId): array
{
try {
$order = $this->findOrder($orderId);
$this->validateOrder($order);
$this->updateOrder($order);
return ['success' => true, 'order' => $order];
} catch (NotFoundException $e) {
return ['success' => false, 'error' => '订单不存在'];
} catch (ValidationException $e) {
return ['success' => false, 'error' => $e->getMessage()];
} catch (Throwable $e) {
error_log($e->__toString());
return ['success' => false, 'error' => '系统错误'];
}
}