# PHP自定义异常类:扩展 Exception 实现业务异常
在PHP开发中,异常处理是保证程序健壮性的重要机制。虽然PHP提供了内置的Exception类,但在实际业务开发中,我们往往需要更精细化的异常控制。今天我们就来深入探讨如何通过扩展Exception类来实现业务异常处理。
## 为什么需要自定义异常类?
PHP内置的Exception类虽然能处理基本的异常情况,但在复杂业务系统中存在几个不足:
1. 无法区分不同类型的业务异常
2. 缺少业务相关的上下文信息
3. 异常处理缺乏统一标准
4. 难以实现异常分类统计
自定义异常类可以解决这些问题,让异常处理更加结构化、标准化。
## 基础:PHP异常类结构
首先,让我们看看PHP内置Exception类的基本结构:
```php
class Exception {
protected $message = 'Unknown exception'; // 异常信息
private $string; // __toString缓存
protected $code = 0; // 用户自定义异常代码
protected $file; // 发生异常的文件名
protected $line; // 发生异常的代码行号
private $trace; // 回溯
private $previous; // 前一个异常
public function __construct($message = null, $code = 0, Exception $previous = null);
final public function getMessage(); // 返回异常信息
final public function getCode(); // 返回异常代码
final public function getFile(); // 返回发生异常的文件名
final public function getLine(); // 返回发生异常的代码行号
final public function getTrace(); // 返回backtrace()数组
final public function getTraceAsString(); // 返回格式化后的backtrace字符串
public function __toString(); // 可输出的格式化字符串
}
```
## 创建基础业务异常类
我们先创建一个基础的业务异常类,继承自Exception:
```php
/**
* 基础业务异常类
*/
class BusinessException extends Exception {
// 默认错误码
const DEFAULT_ERROR_CODE = 10000;
// 额外业务数据
protected $data;
/**
* 构造函数
* @param string $message 错误信息
* @param int $code 错误码
* @param mixed $data 相关业务数据
* @param Exception|null $previous 前一个异常
*/
public function __construct(
string $message = '',
int $code = self::DEFAULT_ERROR_CODE,
$data = null,
Exception $previous = null
) {
parent::__construct($message, $code, $previous);
$this->data = $data;
}
/**
* 获取业务数据
* @return mixed
*/
public function getData() {
return $this->data;
}
/**
* 转化为数组
* @return array
*/
public function toArray(): array {
return [
'code' => $this->getCode(),
'message' => $this->getMessage(),
'data' => $this->getData(),
'file' => $this->getFile(),
'line' => $this->getLine(),
];
}
}
```
## 创建具体业务异常类
有了基础业务异常类后,我们可以针对不同业务场景创建具体的异常类:
```php
// 用户相关异常
class UserException extends BusinessException {
const USER_NOT_FOUND = 10001;
const USER_BANNED = 10002;
const INVALID_PASSWORD = 10003;
public static function userNotFound(string $username): self {
return new self("用户 {$username} 不存在", self::USER_NOT_FOUND);
}
public static function userBanned(int $userId): self {
return new self("用户 {$userId} 已被封禁", self::USER_BANNED, ['user_id' => $userId]);
}
}
// 订单相关异常
class OrderException extends BusinessException {
const ORDER_NOT_FOUND = 20001;
const INVALID_ORDER_STATUS = 20002;
public static function orderNotFound(int $orderId): self {
return new self("订单 {$orderId} 不存在", self::ORDER_NOT_FOUND);
}
}
```
## 异常处理最佳实践
在实际使用中,我们应该遵循以下最佳实践:
1. **分层捕获异常**:在不同层级(如控制器、服务层)捕获不同类型的异常
```php
try {
$orderService->processOrder($orderId);
} catch (OrderException $e) {
// 处理订单相关异常
return response()->json($e->toArray(), 400);
} catch (UserException $e) {
// 处理用户相关异常
return response()->json($e->toArray(), 403);
} catch (BusinessException $e) {
// 处理其他业务异常
return response()->json($e->toArray(), 400);
} catch (Exception $e) {
// 处理系统异常
return response()->json([
'code' => 500,
'message' => '系统错误'
], 500);
}
```
2. **记录详细日志**:对于系统级异常,应该记录详细日志
```php
catch (Exception $e) {
Log::error($e->getMessage(), [
'exception' => $e,
'trace' => $e->getTraceAsString(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
throw $e;
}
```
3. **统一异常响应**:在框架层面统一处理异常响应格式
4. **异常分类统计**:通过不同异常类型进行错误统计
## 进阶:带HTTP状态码的异常类
在API开发中,我们还可以创建带有HTTP状态码的异常类:
```php
class HttpBusinessException extends BusinessException {
protected $httpStatusCode;
public function __construct(
string $message = '',
int $code = self::DEFAULT_ERROR_CODE,
$data = null,
int $httpStatusCode = 400,
Exception $previous = null
) {
parent::__construct($message, $code, $data, $previous);
$this->httpStatusCode = $httpStatusCode;
}
public function getHttpStatusCode(): int {
return $this->httpStatusCode;
}
}
```
## 实际应用示例
让我们看一个完整的用户登录示例:
```php
class AuthService {
public function login(string $username, string $password) {
try {
$user = $this->findUserByUsername($username);
if (!$user) {
throw UserException::userNotFound($username);
}
if ($user->isBanned()) {
throw UserException::userBanned($user->id);
}
if (!$this->verifyPassword($password, $user->password)) {
throw UserException::invalidPassword();
}
return $this->generateToken($user);
} catch (UserException $e) {
// 可以在此处添加日志记录等操作
throw $e;
}
}
}
// 在控制器中
try {
$token = $authService->login($request->username, $request->password);
return response()->json(['token' => $token]);
} catch (UserException $e) {
return response()->json($e->toArray(), $e instanceof HttpBusinessException ?
$e->getHttpStatusCode() : 400);
}
```
## 总结
通过自定义异常类,我们可以:
1. 将业务异常与系统异常区分开来
2. 携带更多业务上下文信息
3. 实现异常分类处理
4. 统一异常响应格式
5. 便于异常统计和分析
在实际项目中,建议根据业务模块划分异常类,并建立完整的异常处理机制。这样不仅能提高代码可维护性,还能让错误处理更加优雅和一致。
希望本文能帮助你更好地理解和使用PHP自定义异常类。如果有任何问题或建议,欢迎在评论区留言讨论!