PHP项目中的异常如何处理?

wen PHP项目 1

本文目录导读:

PHP项目中的异常如何处理?

  1. 基础:try-catch-finally 结构
  2. 自定义异常类
  3. set_exception_handler 全局异常处理
  4. 借助 Error 类处理致命错误
  5. 真实项目中的最佳实践
  6. PHP 8 的新特性:Match 表达式与异常的配合
  7. 推荐的处理流程

在PHP项目中处理异常是一个非常重要的技能,它能帮助你的代码更健壮、更易维护,下面我会从基础到实践,系统地讲解PHP异常处理的几种方式。


基础:try-catch-finally 结构

这是最核心的异常处理方式。

try {
    // 可能抛出异常的代码
    $result = 10 / 0;  // 除零错误
} catch (DivisionByZeroError $e) {
    // 捕获特定类型的异常
    echo "数学错误: " . $e->getMessage();
} catch (Exception $e) {
    // 捕获其他所有异常(更宽泛的类型)
    echo "其他异常: " . $e->getMessage();
} finally {
    // 无论是否发生异常都会执行(可选)
    echo "清理资源...";
}

重要提示:PHP 7.1+ 支持 finally 块,用于释放资源(关闭文件、数据库连接等)。


自定义异常类

在实际项目中,你可能会需要创建自己的异常类型,方便区分不同的错误场景。

class ValidationException extends Exception {}
class DatabaseException extends Exception {}
class UserService {
    public function createUser($data) {
        if (empty($data['email'])) {
            throw new ValidationException('邮箱不能为空');
        }
        try {
            // 数据库操作...
        } catch (PDOException $e) {
            // 将底层异常包装成业务异常
            throw new DatabaseException('用户创建失败', 0, $e);
        }
    }
}

为什么这样做?

  • 业务逻辑层不需要知道底层数据库的细节
  • 上层代码可以按业务异常类型统一处理

set_exception_handler 全局异常处理

当你没有使用 try-catch 捕获异常时,PHP会调用这个全局处理器。

// 在项目入口文件(如 index.php)设置
set_exception_handler(function($exception) {
    // 记录错误日志
    error_log($exception->getMessage() . ' in ' . $exception->getFile() . ':' . $exception->getLine());
    // 根据环境返回不同响应
    if (getenv('APP_ENV') === 'production') {
        http_response_code(500);
        echo json_encode(['error' => '服务器内部错误']);
    } else {
        echo "未捕获的异常: " . $exception->getMessage();
    }
});
// 故意抛出一个未捕获的异常
throw new Exception("测试全局异常处理器");

适用场景

  • API接口的统一错误响应
  • 生产环境的错误屏蔽和日志记录

借助 Error 类处理致命错误

传统 Exception 无法捕获 PHP 的致命错误(如内存耗尽、类型错误等),从 PHP 7 开始,Error 类可以处理这些。

try {
    // 故意触发类型错误
    $obj = new stdClass();
    $obj->method();  // 调用不存在的方法
} catch (Error $e) {
    echo "捕获到致命错误: " . $e->getMessage();
}

注意ErrorException 都实现了 Throwable 接口,你可以统一捕获 Throwable

try {
    // 任何异常或错误
} catch (Throwable $t) {
    echo "捕获所有错误/异常: " . $t->getMessage();
}

真实项目中的最佳实践

1 分层处理

Controller层 → Service层 → Repository层
  • Repository层:捕获底层数据库异常,抛出自定义 DatabaseException
  • Service层:捕获业务逻辑异常,抛出 BusinessExceptionValidationException
  • Controller层:使用 try-catch 捕获所有异常,返回友好的HTTP响应
// Controller示例
public function createUserAction(Request $request) {
    try {
        $userService = new UserService();
        $user = $userService->createUser($request->getBody());
        return $this->json(['data' => $user], 201);
    } catch (ValidationException $e) {
        return $this->json(['error' => $e->getMessage()], 400);
    } catch (DatabaseException $e) {
        // 记录详细日志给运维,但返回通用信息给用户
        Logger::error($e->getMessage(), ['trace' => $e->getTraceAsString()]);
        return $this->json(['error' => '服务暂时不可用'], 500);
    } catch (Throwable $t) {
        Logger::critical($t->getMessage());
        return $this->json(['error' => '未知错误'], 500);
    }
}

2 日志记录

无论是否在开发环境,都应该记录异常日志。

try {
    // 业务逻辑
} catch (Exception $e) {
    // 使用 Monolog 或其他日志库
    $logger->error($e->getMessage(), [
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString()
    ]);
    // 重新抛出或返回错误响应
}

3 不要吞没异常

// ❌ 错误:空 catch 块会隐藏问题
try {
    riskyOperation();
} catch (Exception $e) {
    // 什么都不做——这样很难调试
}
// ✅ 正确:至少记录日志
try {
    riskyOperation();
} catch (Exception $e) {
    error_log($e->getMessage());
    // 如果无法恢复,应该重新抛出或返回错误
    throw $e;
}

4 使用异常链保留上下文

try {
    connectDatabase();
    queryUsers();
} catch (PDOException $e) {
    // 把底层异常作为第三个参数传入,保留调用栈
    throw new ServiceException('数据库操作失败', 500, $e);
}

这样上层可以同时看到业务错误信息和底层技术细节(在日志中)。


PHP 8 的新特性:Match 表达式与异常的配合

PHP 8 的 match 表达式在异常处理中也很有用:

function handleHttpCode(int $code): string {
    return match ($code) {
        400 => throw new BadRequestException(),
        404 => throw new NotFoundException(),
        500 => throw new InternalServerErrorException(),
        default => '正常'
    };
}

推荐的处理流程

  1. 定义自定义异常类ValidationExceptionDatabaseExceptionAuthException
  2. 在业务层抛出特定异常,不要抛出通用 Exception
  3. 在控制器或入口try-catch 捕获,并决定是继续抛出还是返回HTTP响应
  4. 设置全局异常处理器 兜底未捕获的异常
  5. 始终记录日志,尤其是在生产环境

这样,你的PHP项目无论在开发还是生产环境,都能清晰、优雅地处理各种异常情况。

抱歉,评论功能暂时关闭!