# 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自定义异常类。如果有任何问题或建议,欢迎在评论区留言讨论!