PHP项目怎么处理异常和错误?

wen java案例 2

本文目录导读:

PHP项目怎么处理异常和错误?

  1. 核心概念区分
  2. 处理错误的两种方式
  3. 处理异常的三种方式
  4. 最佳实践总结
  5. 完整示例:一个通用的错误异常处理类

在PHP项目中处理异常和错误是一个非常重要的环节,良好的处理机制能提升代码的健壮性和可维护性,下面我会从错误异常两个方面来详细说明,并给出最佳实践。


核心概念区分

特性 错误 (Error) 异常 (Exception)
触发方式 PHP 引擎自身触发 (如:致命错误、警告) 手动 throw 或某些内置函数抛出
处理方式 错误处理器 (set_error_handler) try-catch
是否能被 catch 不能直接 catch,但可以通过错误处理器捕获 可以 catch
典型场景 文件未找到、语法错误、内存耗尽 数据库连接失败、业务逻辑错误

PHP 7 以后引入了 Throwable 接口,错误异常都实现了该接口,因此可以用 catch (Throwable $e) 统一捕获。


处理错误的两种方式

使用 set_error_handler 自定义错误处理器

适用于处理 PHP 非致命错误(如 warning、notice):

// 自定义错误处理器
set_error_handler(function(int $errno, string $errstr, string $errfile, int $errline) {
    // 记录日志
    error_log("Error [$errno] $errstr in $errfile on line $errline");
    // 可以根据错误级别决定是否抛异常
    if ($errno === E_WARNING) {
        throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    }
    // 返回 true 表示已处理,PHP 不再执行标准错误处理
    return true;
});
// 触发一个 warning
$arr = [];
echo $arr['not_exists'];  // 这个 warning 会被自定义处理器捕获

注意:此方式无法捕获致命错误(如 E_ERROR、核心错误)。

捕获致命错误:register_shutdown_function

对于致命错误(如内存溢出、语法错误),可以注册关闭函数:

register_shutdown_function(function() {
    $error = error_get_last();
    if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        // 记录致命错误并友好提示
        error_log("Fatal Error: " . json_encode($error));
        http_response_code(500);
        echo "系统繁忙,请稍后再试。";
    }
});

处理异常的三种方式

try-catch-finally 基本结构

try {
    $db = new PDO('mysql:host=invalid', 'user', 'pass');
} catch (PDOException $e) {
    // 处理特定异常
    error_log("数据库连接失败: " . $e->getMessage());
    die("数据库连接失败,请联系管理员。");
} catch (Exception $e) {
    // 处理其他异常
    error_log("异常: " . $e->getMessage());
} finally {
    // 无论是否捕获异常都会执行(可选)
    echo "执行结束。";
}

设置默认异常处理器 set_exception_handler

当 try-catch 未捕获到的异常,PHP 会交给默认异常处理器,可以自定义:

set_exception_handler(function(Throwable $e) {
    // 记录日志
    error_log("未捕获异常: " . $e->getMessage());
    // 返回用户友好的 500 页面
    http_response_code(500);
    if (defined('ENVIRONMENT') && ENVIRONMENT === 'development') {
        echo "异常信息: " . $e->getMessage() . "<br>";
        echo "文件: " . $e->getFile() . " 行: " . $e->getLine();
    } else {
        echo "服务器内部错误,请稍后再试。";
    }
});
// 测试:抛出一个未捕获的异常
throw new RuntimeException("测试异常");

自定义异常类

扩展业务逻辑的可读性:

class InsufficientBalanceException extends Exception {}
class BankAccount {
    public function withdraw($amount) {
        if ($amount > $this->balance) {
            throw new InsufficientBalanceException("余额不足,当前余额: {$this->balance}");
        }
        // 正常逻辑
    }
}
try {
    $account->withdraw(1000);
} catch (InsufficientBalanceException $e) {
    echo $e->getMessage(); // 余额不足...
}

最佳实践总结

分层处理(推荐结构)

  • 开发环境:显示详细错误信息和堆栈。
  • 生产环境:记录日志,返回用户友好的错误页面(500 错误页)。
// config.php
define('DEBUG_MODE', true);  // 上线改为 false
// 错误与异常处理器
if (DEBUG_MODE) {
    // 开发环境:显示所有错误
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
} else {
    // 生产环境:隐藏错误,写入日志
    error_reporting(E_ALL);
    ini_set('display_errors', 0);
    ini_set('log_errors', 1);
    ini_set('error_log', '/path/to/php-error.log');
}
// 统一异常处理器
set_exception_handler(function(Throwable $e) {
    error_log("[" . get_class($e) . "] " . $e->getMessage());
    http_response_code(500);
    if (DEBUG_MODE) {
        echo "异常: " . $e->getMessage() . "<br>";
        echo "文件: " . $e->getFile() . ":" . $e->getLine();
    } else {
        // 加载自定义错误页面
        include 'errors/500.html';
    }
});

永远不要向用户暴露真实错误信息

// ❌ 错误做法
echo "数据库错误: " . $e->getMessage();
// ✅ 正确做法
error_log($e->getMessage());  // 记录到日志
echo "系统处理请求时发生错误,请稍后再试。";

使用日志系统

推荐使用 monolog 等专业日志库(通常框架自带):

// 例如使用 monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('/path/to/app.log', Logger::WARNING));
try {
    // 业务代码
} catch (Exception $e) {
    $log->error($e->getMessage(), ['trace' => $e->getTraceAsString()]);
}

框架中的自动化处理

如果是现代框架(如 Laravel、Symfony、ThinkPHP),它们已经内置了完善的错误与异常处理机制:

  • Laravelapp/Exceptions/Handler.php 中的 render()report() 方法。
  • ThinkPHP:配置 app.exception_handle 全局接管。

你只需要在框架提供的异常处理类中编写你的业务逻辑即可。


完整示例:一个通用的错误异常处理类

class ErrorHandler {
    public static function register() {
        // 设置错误处理
        set_error_handler([self::class, 'handleError']);
        // 设置异常处理(针对未捕获的异常)
        set_exception_handler([self::class, 'handleException']);
        // 捕获致命错误
        register_shutdown_function([self::class, 'handleFatalError']);
    }
    public static function handleError($level, $message, $file, $line) {
        if (error_reporting() & $level) {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
    }
    public static function handleException(Throwable $e) {
        error_log("Uncaught Exception: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
        http_response_code(500);
        if (defined('DEBUG_MODE') && DEBUG_MODE) {
            echo "<pre>" . $e . "</pre>";
        } else {
            echo "系统错误,请稍后重试。";
        }
    }
    public static function handleFatalError() {
        $error = error_get_last();
        if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            self::handleException(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']));
        }
    }
}
// 启动错误处理
ErrorHandler::register();

场景 处理方法
线上隐藏错误详情 关闭 display_errors,开启 log_errors
所有错误都转异常 set_error_handler 抛出 ErrorException
未捕获的异常 set_exception_handler 统一处理
致命错误 register_shutdown_function
业务逻辑异常 自定义异常类 + try-catch
框架环境 利用框架提供的 Handler(如 Laravel 的 App\Exceptions\Handler)

推荐做法:线上环境统一捕获所有 Throwable,记录日志,返回友好界面,开发环境则显示完整错误信息以便调试。

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