PHP 异常处理:try-catch-throw 及自定义异常类
异常 vs 错误
PHP 5+ 引入异常机制,用于处理可预期的错误情况。传统 PHP 错误(trigger_error)与异常是两套不同的机制。
- 错误(Error):系统级问题,如内存耗尽、语法错误,通常不可恢复
- 异常(Exception):应用级问题,如文件不存在、数据验证失败,可以捕获并处理
一、基础 try-catch
<?php\ntry {\n // 可能抛出异常的代码\n $result = 10 / 0; // 不会抛异常,会产生 warning\n\n $file = fopen('not_exist.txt', 'r'); // 可能失败\n if (!$file) {\n throw new Exception('文件打开失败'); // 手动抛出异常\n }\n\n $content = file_get_contents('data.json');\n if ($content === false) {\n throw new Exception('文件读取失败');\n }\n\n} catch (Exception $e) {\n // 捕获异常并处理\n echo '出错了:' . $e->getMessage();\n}\n?>
二、Exception 类
<?php\ntry {\n throw new Exception('发生错误', 1001);\n} catch (Exception $e) {\n echo $e->getMessage(); // 错误信息\n echo $e->getCode(); // 错误码:1001\n echo $e->getFile(); // 出错文件:/path/to/file.php\n echo $e->getLine(); // 出错行数:3\n echo $e->getTraceAsString(); // 完整堆栈跟踪\n}\n?>
三、多 catch 块(异常匹配)
<?php\ntry {\n $conn = new PDO('mysql:host=wronghost;dbname=test', 'user', 'pass');\n $result = $conn->query('SELECT * FROM not_exist_table');\n} catch (PDOException $e) {\n // PDO 相关错误(数据库连接失败、SQL 语法错误)\n echo '数据库错误:' . $e->getMessage();\n log_error('DB Error', $e);\n} catch (Exception $e) {\n // 其他所有异常\n echo '系统错误:' . $e->getMessage();\n} catch (Throwable $e) {\n // Throwable = Exception + Error(PHP 7+)\n // 捕获所有可抛出对象,包括 Error\n echo '致命错误:' . $e->getMessage();\n}\n?>
四、finally 块
无论是否抛出异常,finally 块都会执行,常用于清理资源:
<?php\n$conn = null;\ntry {\n $conn = new PDO($dsn, $user, $pass);\n $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);\n $user = $conn->query('SELECT * FROM users WHERE id=1')->fetch();\n} catch (PDOException $e) {\n echo '查询失败:' . $e->getMessage();\n $user = null;\n} finally {\n // 无论成功还是失败,都关闭连接\n $conn = null;\n echo '数据库连接已关闭';\n}\n?>
return 在 try-finally 中的行为
<?php\nfunction test() {\n try {\n return 'try返回值';\n } finally {\n echo 'finally 还是会执行';\n }\n}\necho test(); // 先输出"finally 还是会执行",再输出"try返回值"\n\n// finally 在 return 之前执行\nfunction divide($a, $b) {\n try {\n if ($b == 0) throw new Exception('除数不能为零');\n return $a / $b;\n } finally {\n echo '清理资源\\n'; // 先执行\n }\n}\necho divide(10, 2); // 输出"清理资源"然后输出"5"\n?>
五、重新抛出异常
<?php\ntry {\n try {\n // 业务逻辑\n throw new Exception('原始错误');\n } catch (Exception $e) {\n // 添加更多信息后重新抛出\n throw new Exception('处理失败:' . $e->getMessage(), 0, $e);\n // 第三个参数是前一个异常(异常链)\n }\n} catch (Exception $e) {\n echo $e->getMessage(); // 处理失败:原始错误\n echo $e->getPrevious()->getMessage(); // 原始错误\n}\n?>
六、自定义异常类
<?php\n// 基础业务异常类\nclass AppException extends Exception {\n protected $error_code = 'APP_ERROR';\n\n public function getErrorCode(): string {\n return $this->error_code;\n }\n}\n\n// 具体异常类\nclass ValidationException extends AppException {\n protected $error_code = 'VALIDATION_ERROR';\n private array $errors;\n\n public function __construct(string $message, array $errors = []) {\n parent::__construct($message);\n $this->errors = $errors;\n }\n\n public function getFieldErrors(): array {\n return $this->errors;\n }\n}\n\nclass NotFoundException extends AppException {\n protected $error_code = 'NOT_FOUND';\n}\n\nclass UnauthorizedException extends AppException {\n protected $error_code = 'UNAUTHORIZED';\n}\n?>
<?php\ntry {\n $email = $_POST['email'] ?? '';\n if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {\n throw new ValidationException('邮箱格式不正确', ['email' => '无效的邮箱格式']);\n }\n\n $user = find_user_by_email($email);\n if (!$user) {\n throw new NotFoundException('用户不存在');\n }\n\n} catch (ValidationException $e) {\n // 返回 422 和字段错误\n http_response_code(422);\n echo json_encode(['error' => $e->getMessage(), 'fields' => $e->getFieldErrors()]);\n} catch (NotFoundException $e) {\n http_response_code(404);\n echo json_encode(['error' => $e->getMessage()]);\n} catch (AppException $e) {\n http_response_code(500);\n echo json_encode(['error' => $e->getMessage(), 'code' => $e->getErrorCode()]);\n}\n?>
七、全局异常处理器
<?php\n// 设置全局异常处理器\nset_exception_handler(function(Throwable $e) {\n // 记录日志\n error_log($e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());\n\n // API 请求返回 JSON\n if (strpos($_SERVER['REQUEST_URI'] ?? '', '/api/') === 0) {\n header('Content-Type: application/json');\n echo json_encode([\n 'error' => $e->getMessage(),\n 'code' => $e instanceof AppException ? $e->getErrorCode() : 'INTERNAL_ERROR',\n ], JSON_UNESCAPED_UNICODE);\n exit;\n }\n\n // 页面请求显示友好错误\n echo '<h1>出错了</h1>';\n echo '<p>' . htmlspecialchars($e->getMessage()) . '</p>';\n});\n\n// 恢复默认处理器\n// restore_exception_handler();\n?>
八、Error vs Exception
<?php\n// PHP 7+ 引入了 Error 类(类型错误、内存错误等)\ntry {\n // TypeError\n function add(int $a, int $b): int { return $a + $b; }\n add('hello', 'world'); // TypeError\n} catch (TypeError $e) {\n echo '类型错误:' . $e->getMessage();\n}\n\n// catch (Throwable) 可以同时捕获 Exception 和 Error\ntry {\n // 可能抛出 Exception 或 Error\n} catch (Throwable $e) {\n echo get_class($e); // 区分具体类型\n}\n?>
总结
- 异常用于可预期的错误,错误处理用于系统级问题
- catch 块按子类到父类顺序匹配(先 PDOException 再 Exception)
- finally 块无论是否异常都会执行,常用于释放资源
- 自定义异常类让错误处理更精确,配合 HTTP 状态码使用
- 用
set_exception_handler()捕获所有未处理异常
Throwable 捕获 Exception 和 Error