PHPset_exception_handler()函数

简介

set_exception_handler() 是PHP内置的异常处理函数,用于设置用户自定义的异常处理函数。通过这个函数,开发者可以定义自己的异常处理逻辑,捕获未被try/catch块捕获的异常,从而实现更优雅的异常处理和错误恢复。

这个函数允许你接管PHP的异常处理流程,可以用于记录异常日志、显示友好的错误页面、发送异常通知等。自定义异常处理器会捕获所有未被捕获的异常,包括由throw语句抛出的异常和某些类型的错误转换而来的异常。
注意:自定义异常处理器会捕获整个脚本中未捕获的异常。一旦异常被异常处理器捕获,脚本通常会终止执行,除非在异常处理器中明确继续执行。

语法

callable|null set_exception_handler(callable $exception_handler)

函数返回之前定义的异常处理程序,或者在出错时返回null

参数说明

参数 类型 默认值 描述
$exception_handler callable 必填 自定义的异常处理函数。可以是一个函数名、类方法数组或匿名函数。这个函数需要接受1个参数:
  • $exception - 抛出的异常对象(Throwable实例)

返回值

callable|null - 返回之前定义的异常处理程序,或者在出错时返回null

可能的返回值:

  • callable - 之前设置的自定义异常处理器
  • null - 如果之前没有设置自定义异常处理器,或者发生错误

示例

示例1:基本用法

演示如何使用set_exception_handler()设置基本的自定义异常处理器:

<?php
// 自定义异常处理函数
function customExceptionHandler($exception) {
    echo "<div style='background:#f8d7da; padding:10px; margin:5px; border:1px solid #f5c6cb;'>";
    echo "<strong>自定义异常处理器捕获到异常:</strong><br>";
    echo "异常类型: " . get_class($exception) . "<br>";
    echo "异常消息: " . htmlspecialchars($exception->getMessage()) . "<br>";
    echo "异常文件: " . $exception->getFile() . "<br>";
    echo "异常行号: " . $exception->getLine() . "<br>";
    echo "<hr>";
    echo "<strong>堆栈跟踪:</strong><br>";
    echo "<pre style='font-size:0.9em;'>" . htmlspecialchars($exception->getTraceAsString()) . "</pre>";
    echo "</div>";
}

// 设置自定义异常处理器
$previousHandler = set_exception_handler("customExceptionHandler");

echo "<h4>测试异常处理:</h4>";

// 抛出一个异常(不会被try/catch捕获)
throw new Exception("这是一个测试异常,将被自定义异常处理器捕获");

// 注意:这行代码不会执行,因为异常处理器执行后脚本通常会终止
echo "这行代码不会执行";

// 恢复之前的异常处理器
// restore_exception_handler();

示例2:使用匿名函数

演示如何使用匿名函数作为异常处理器:

<?php
// 使用匿名函数作为异常处理器
$exceptionHandler = function($exception) {
    $log = sprintf(
        "[%s] 未捕获异常: %s in %s on line %d\n堆栈跟踪:\n%s\n",
        date('Y-m-d H:i:s'),
        $exception->getMessage(),
        $exception->getFile(),
        $exception->getLine(),
        $exception->getTraceAsString()
    );

    // 记录到日志文件
    file_put_contents('exceptions.log', $log, FILE_APPEND);

    // 根据环境显示不同的错误信息
    if (getenv('APP_ENV') === 'development') {
        // 开发环境:显示详细错误
        echo "<div style='background:#fff3cd; padding:10px; margin:5px; border:1px solid #ffeaa7;'>";
        echo "<h3>未捕获异常</h3>";
        echo "<pre>" . htmlspecialchars($log) . "</pre>";
        echo "</div>";
    } else {
        // 生产环境:显示用户友好信息
        echo "<div style='text-align:center; padding:50px;'>";
        echo "<h1 style='color:#dc3545;'>系统错误</h1>";
        echo "<p>抱歉,系统发生了内部错误。</p>";
        echo "<p>错误已记录,我们的技术团队将尽快处理。</p>";
        echo "</div>";
    }

    // 发送HTTP 500错误状态码
    if (!headers_sent()) {
        http_response_code(500);
    }
};

// 设置异常处理器
set_exception_handler($exceptionHandler);

echo "<h4>测试匿名函数异常处理器:</h4>";

// 设置环境变量
putenv('APP_ENV=development');

// 定义一个会抛出异常的函数
function riskyOperation() {
    // 随机决定是否抛出异常
    if (rand(0, 1)) {
        throw new RuntimeException("随机操作失败");
    }
    return "操作成功";
}

// 调用函数(可能抛出异常)
riskyOperation();

echo "<div class='alert alert-info'>";
echo "如果看到异常信息,说明异常处理器正在工作。";
echo "</div>";

示例3:在类中使用

演示如何在类中使用set_exception_handler()

<?php
// 异常处理类
class ExceptionHandler {
    private $logFile = 'app_exceptions.log';
    private $environment = 'production';

    public function __construct($logFile = null, $environment = null) {
        if ($logFile) {
            $this->logFile = $logFile;
        }

        if ($environment) {
            $this->environment = $environment;
        } else {
            $this->environment = getenv('APP_ENV') ?: 'production';
        }

        // 设置异常处理器
        set_exception_handler([$this, 'handleException']);
    }

    public function handleException($exception) {
        // 记录异常
        $this->logException($exception);

        // 处理异常
        $this->processException($exception);

        // 根据环境决定是否终止脚本
        if ($this->environment === 'production') {
            exit(1);
        }
    }

    private function logException($exception) {
        $logEntry = $this->formatExceptionLog($exception);

        // 确保日志目录存在
        $logDir = dirname($this->logFile);
        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }

        // 记录到文件
        file_put_contents($this->logFile, $logEntry, FILE_APPEND);

        // 同时记录到系统错误日志
        error_log("未捕获异常: " . $exception->getMessage());
    }

    private function formatExceptionLog($exception) {
        $timestamp = date('Y-m-d H:i:s');
        $exceptionClass = get_class($exception);
        $message = $exception->getMessage();
        $file = $exception->getFile();
        $line = $exception->getLine();
        $trace = $exception->getTraceAsString();

        $log = "========================================\n";
        $log .= "时间: $timestamp\n";
        $log .= "异常类型: $exceptionClass\n";
        $log .= "消息: $message\n";
        $log .= "位置: $file:$line\n";
        $log .= "堆栈跟踪:\n$trace\n";
        $log .= "========================================\n\n";

        return $log;
    }

    private function processException($exception) {
        switch ($this->environment) {
            case 'development':
                $this->displayDebugException($exception);
                break;
            case 'testing':
                $this->displayTestingException($exception);
                break;
            case 'production':
                $this->displayProductionException($exception);
                break;
            default:
                $this->displayDefaultException($exception);
        }
    }

    private function displayDebugException($exception) {
        echo "<!DOCTYPE html>";
        echo "<html><head><title>未捕获异常 - 调试模式</title><style>";
        echo "body { font-family: monospace; margin: 20px; }";
        echo ".exception { background: #f8f9fa; border: 1px solid #ddd; padding: 20px; margin: 20px 0; }";
        echo ".exception-header { background: #dc3545; color: white; padding: 10px; margin: -20px -20px 20px -20px; }";
        echo ".exception-message { color: #721c24; background: #f8d7da; padding: 10px; border: 1px solid #f5c6cb; }";
        echo ".exception-trace { background: #e9ecef; padding: 10px; margin-top: 10px; border: 1px solid #ddd; }";
        echo "</style></head><body>";

        echo "<div class='exception'>";
        echo "<div class='exception-header'>";
        echo "<h2>未捕获异常: " . get_class($exception) . "</h2>";
        echo "</div>";

        echo "<div class='exception-message'>";
        echo "<strong>消息:</strong> " . htmlspecialchars($exception->getMessage()) . "<br>";
        echo "<strong>位置:</strong> " . $exception->getFile() . " 第 " . $exception->getLine() . " 行";
        echo "</div>";

        echo "<div class='exception-trace'>";
        echo "<strong>堆栈跟踪:</strong><br>";
        echo "<pre>" . htmlspecialchars($exception->getTraceAsString()) . "</pre>";
        echo "</div>";

        echo "</div>";
        echo "</body></html>";
    }

    private function displayTestingException($exception) {
        // 测试环境:记录但不显示
        echo "测试环境:异常已记录到日志";
    }

    private function displayProductionException($exception) {
        // 生产环境:用户友好页面
        echo "<!DOCTYPE html>";
        echo "<html><head><title>系统错误</title><style>";
        echo "body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }";
        echo "h1 { color: #dc3545; }";
        echo "</style></head><body>";
        echo "<h1>系统错误</h1>";
        echo "<p>抱歉,系统发生了内部错误。</p>";
        echo "<p>我们的技术团队已收到通知,正在处理此问题。</p>";
        echo "</body></html>";
    }

    private function displayDefaultException($exception) {
        // 默认处理
        echo "发生了一个异常: " . htmlspecialchars($exception->getMessage());
    }

    public function __destruct() {
        // 恢复异常处理器
        restore_exception_handler();
    }
}

// 使用示例
echo "<h4>使用ExceptionHandler类:</h4>";

// 设置环境变量
putenv('APP_ENV=development');

// 创建异常处理器实例
$handler = new ExceptionHandler('logs/exceptions.log');

// 定义一个会抛出异常的函数
function processData($data) {
    if (empty($data)) {
        throw new InvalidArgumentException("数据不能为空");
    }

    if (!is_array($data)) {
        throw new InvalidArgumentException("数据必须是数组");
    }

    return "处理了 " . count($data) . " 条数据";
}

// 测试异常处理
try {
    // 这个异常会被捕获
    processData('');
} catch (Exception $e) {
    echo "<div class='alert alert-warning'>";
    echo "异常被try/catch捕获: " . $e->getMessage();
    echo "</div>";
}

// 这个异常不会被try/catch捕获,会被异常处理器处理
processData(null);

echo "<div class='alert alert-info'>";
echo "查看 logs/exceptions.log 文件获取异常日志";
echo "</div>";

示例4:完整的异常处理系统

创建一个完整的异常处理系统,包括不同类型异常的处理:

<?php
/**
 * 完整的异常处理系统
 */
class AppExceptionHandler {
    private static $instance = null;
    private $logFile = 'logs/exceptions.log';
    private $errorLogFile = 'logs/errors.log';
    private $environment = 'production';

    private function __construct() {
        $this->environment = getenv('APP_ENV') ?: 'production';

        // 确保日志目录存在
        $this->ensureLogDirectory();

        // 设置异常处理器
        $this->setupExceptionHandling();
    }

    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function ensureLogDirectory() {
        $logDir = dirname($this->logFile);
        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }
    }

    private function setupExceptionHandling() {
        // 设置异常处理器
        set_exception_handler([$this, 'handleException']);

        // 设置错误处理器(将错误转换为异常)
        set_error_handler([$this, 'handleError']);

        // 设置关闭函数
        register_shutdown_function([$this, 'handleShutdown']);
    }

    public function handleException($exception) {
        $this->logException($exception);
        $this->displayException($exception);
        $this->notifyException($exception);

        // 在命令行模式下,退出脚本
        if (php_sapi_name() === 'cli') {
            exit(1);
        }
    }

    public function handleError($errno, $errstr, $errfile, $errline) {
        // 将错误转换为异常
        if (error_reporting() & $errno) {
            throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
        }

        return false; // 继续执行PHP内部错误处理器
    }

    public function handleShutdown() {
        $error = error_get_last();

        if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            $exception = new ErrorException(
                $error['message'],
                0,
                $error['type'],
                $error['file'],
                $error['line']
            );

            $this->handleException($exception);
        }
    }

    private function logException($exception) {
        $logEntry = $this->formatLogEntry($exception);

        // 记录到异常日志
        file_put_contents($this->logFile, $logEntry, FILE_APPEND);

        // 如果是严重错误,也记录到错误日志
        if ($this->isCriticalException($exception)) {
            file_put_contents($this->errorLogFile, $logEntry, FILE_APPEND);
        }
    }

    private function formatLogEntry($exception) {
        $timestamp = date('Y-m-d H:i:s');
        $exceptionClass = get_class($exception);
        $message = $exception->getMessage();
        $file = $exception->getFile();
        $line = $exception->getLine();
        $code = $exception->getCode();
        $trace = $exception->getTraceAsString();

        $log = "[" . $timestamp . "] " . $exceptionClass . " (" . $code . ")\n";
        $log .= "消息: " . $message . "\n";
        $log .= "位置: " . $file . ":" . $line . "\n";
        $log .= "请求: " . ($_SERVER['REQUEST_URI'] ?? 'CLI') . "\n";
        $log .= "IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . "\n";
        $log .= "用户代理: " . ($_SERVER['HTTP_USER_AGENT'] ?? 'unknown') . "\n";
        $log .= "堆栈跟踪:\n" . $trace . "\n";
        $log .= str_repeat("-", 80) . "\n";

        return $log;
    }

    private function isCriticalException($exception) {
        $criticalExceptions = [
            'Error',
            'ParseError',
            'TypeError',
            'DivisionByZeroError',
            'ErrorException',
        ];

        return in_array(get_class($exception), $criticalExceptions) ||
               $exception->getCode() >= 500;
    }

    private function displayException($exception) {
        if (php_sapi_name() === 'cli') {
            // 命令行模式
            echo "未捕获异常: " . $exception->getMessage() . "\n";
            echo "位置: " . $exception->getFile() . ":" . $exception->getLine() . "\n";
        } else {
            // Web模式
            if ($this->environment === 'development') {
                $this->displayDebugException($exception);
            } else {
                $this->displayProductionException($exception);
            }
        }
    }

    private function displayDebugException($exception) {
        header('Content-Type: text/html; charset=utf-8');

        echo "<!DOCTYPE html>";
        echo "<html><head><title>未捕获异常 - 调试模式</title><style>";
        echo "* { box-sizing: border-box; margin: 0; padding: 0; }";
        echo "body { font-family: 'Consolas', 'Monaco', monospace; background: #f5f5f5; color: #333; line-height: 1.6; }";
        echo ".exception-container { max-width: 1200px; margin: 20px auto; padding: 20px; }";
        echo ".exception-header { background: #dc3545; color: white; padding: 20px; border-radius: 5px 5px 0 0; }";
        echo ".exception-body { background: white; padding: 20px; border: 1px solid #ddd; border-top: none; }";
        echo ".exception-section { margin-bottom: 20px; }";
        echo ".exception-title { color: #dc3545; border-bottom: 2px solid #dc3545; padding-bottom: 5px; margin-bottom: 10px; }";
        echo ".exception-detail { background: #f8f9fa; padding: 10px; border: 1px solid #e9ecef; border-radius: 3px; }";
        echo "pre { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 3px; overflow-x: auto; font-size: 14px; }";
        echo ".trace-item { padding: 8px; border-bottom: 1px solid #eee; }";
        echo ".trace-item:hover { background: #f8f9fa; }";
        echo ".trace-file { color: #28a745; }";
        echo ".trace-function { color: #007bff; }";
        echo "</style></head><body>";

        echo "<div class='exception-container'>";
        echo "<div class='exception-header'>";
        echo "<h1>" . get_class($exception) . "</h1>";
        echo "<p>" . htmlspecialchars($exception->getMessage()) . "</p>";
        echo "</div>";

        echo "<div class='exception-body'>";

        // 异常详情
        echo "<div class='exception-section'>";
        echo "<h2 class='exception-title'>异常详情</h2>";
        echo "<div class='exception-detail'>";
        echo "<p><strong>异常类型:</strong> " . get_class($exception) . "</p>";
        echo "<p><strong>异常代码:</strong> " . $exception->getCode() . "</p>";
        echo "<p><strong>异常文件:</strong> " . $exception->getFile() . "</p>";
        echo "<p><strong>异常行号:</strong> " . $exception->getLine() . "</p>";
        echo "</div>";
        echo "</div>";

        // 堆栈跟踪
        echo "<div class='exception-section'>";
        echo "<h2 class='exception-title'>堆栈跟踪</h2>";
        echo "<div class='exception-detail'>";

        $trace = $exception->getTrace();
        foreach ($trace as $i => $frame) {
            echo "<div class='trace-item'>";
            echo "<strong>#" . $i . "</strong> ";

            if (isset($frame['file'])) {
                echo "<span class='trace-file'>" . $frame['file'] . "(" . ($frame['line'] ?? 0) . ")</span>: ";
            }

            if (isset($frame['class'])) {
                echo "<span class='trace-function'>" . $frame['class'] . $frame['type'] . $frame['function'] . "()</span>";
            } else if (isset($frame['function'])) {
                echo "<span class='trace-function'>" . $frame['function'] . "()</span>";
            }

            echo "</div>";
        }

        echo "</div>";
        echo "</div>";

        // 原始堆栈跟踪
        echo "<div class='exception-section'>";
        echo "<h2 class='exception-title'>原始堆栈跟踪</h2>";
        echo "<pre>" . htmlspecialchars($exception->getTraceAsString()) . "</pre>";
        echo "</div>";

        echo "</div>";
        echo "</div>";
        echo "</body></html>";
    }

    private function displayProductionException($exception) {
        header('Content-Type: text/html; charset=utf-8');
        http_response_code(500);

        echo "<!DOCTYPE html>";
        echo "<html><head><title>系统错误</title><style>";
        echo "body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); height: 100vh; display: flex; align-items: center; justify-content: center; margin: 0; }";
        echo ".error-container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); text-align: center; max-width: 500px; }";
        echo "h1 { color: #dc3545; margin-bottom: 20px; }";
        echo "p { color: #666; margin-bottom: 20px; line-height: 1.6; }";
        echo ".btn { display: inline-block; background: #667eea; color: white; padding: 10px 20px; border-radius: 5px; text-decoration: none; transition: background 0.3s; }";
        echo ".btn:hover { background: #764ba2; }";
        echo "</style></head><body>";

        echo "<div class='error-container'>";
        echo "<h1>系统错误</h1>";
        echo "<p>抱歉,系统发生了内部错误。</p>";
        echo "<p>我们的技术团队已收到通知,正在处理此问题。</p>";
        echo "<p>请稍后再试,或返回首页。</p>";
        echo "<a href='/' class='btn'>返回首页</a>";
        echo "</div>";
        echo "</body></html>";
    }

    private function notifyException($exception) {
        // 发送异常通知(邮件、Slack、Webhook等)
        if ($this->isCriticalException($exception)) {
            $this->sendCriticalExceptionNotification($exception);
        }
    }

    private function sendCriticalExceptionNotification($exception) {
        $notification = [
            'timestamp' => date('c'),
            'environment' => $this->environment,
            'exception' => get_class($exception),
            'message' => $exception->getMessage(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI',
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
        ];

        // 记录到错误日志
        error_log("严重异常: " . json_encode($notification));

        // 在实际应用中,这里可以发送邮件、Slack消息等
        // 例如:mail('admin@example.com', '严重异常通知', print_r($notification, true));
    }
}

// 使用示例
echo "<h4>使用完整的异常处理系统:</h4>";

// 设置环境
putenv('APP_ENV=development');

// 初始化异常处理系统
AppExceptionHandler::getInstance();

// 定义一些自定义异常
class DatabaseException extends Exception {
    protected $query;

    public function __construct($message, $query = null, $code = 0, Exception $previous = null) {
        $this->query = $query;
        parent::__construct($message, $code, $previous);
    }

    public function getQuery() {
        return $this->query;
    }
}

class ValidationException extends Exception {
    protected $errors = [];

    public function __construct($message, $errors = [], $code = 0, Exception $previous = null) {
        $this->errors = $errors;
        parent::__construct($message, $code, $previous);
    }

    public function getErrors() {
        return $this->errors;
    }
}

// 模拟业务逻辑
function processUserRegistration($data) {
    if (empty($data['email'])) {
        throw new ValidationException("邮箱不能为空", ['email' => 'required']);
    }

    if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        throw new ValidationException("邮箱格式不正确", ['email' => 'invalid']);
    }

    // 模拟数据库操作
    if (rand(0, 1)) {
        throw new DatabaseException("数据库连接失败", "INSERT INTO users (...) VALUES (...)");
    }

    return "用户注册成功";
}

echo "<h5>测试不同类型的异常:</h5>";

// 测试1:验证异常
try {
    processUserRegistration(['email' => '']);
} catch (ValidationException $e) {
    echo "<div class='alert alert-warning'>";
    echo "验证异常被捕获: " . $e->getMessage();
    echo "</div>";
}

// 测试2:数据库异常(可能被异常处理器捕获)
processUserRegistration(['email' => 'test@example.com']);

echo "<div class='alert alert-success'>";
echo "查看 logs/ 目录下的日志文件获取详细异常信息";
echo "</div>";

示例5:异常处理器的实际应用

演示在实际应用场景中如何使用异常处理器:

<?php
// API响应类
class ApiResponse {
    public static function success($data = null, $message = '成功', $code = 200) {
        $response = [
            'success' => true,
            'code' => $code,
            'message' => $message,
            'data' => $data,
            'timestamp' => date('c'),
        ];

        header('Content-Type: application/json');
        http_response_code($code);
        echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }

    public static function error($message, $code = 500, $errors = null) {
        $response = [
            'success' => false,
            'code' => $code,
            'message' => $message,
            'errors' => $errors,
            'timestamp' => date('c'),
        ];

        header('Content-Type: application/json');
        http_response_code($code);
        echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }
}

// API异常处理器
class ApiExceptionHandler {
    public static function register() {
        set_exception_handler([self::class, 'handleException']);
        set_error_handler([self::class, 'handleError']);
    }

    public static function handleException($exception) {
        // 记录异常
        self::logException($exception);

        // 根据异常类型返回不同的HTTP状态码
        $httpCode = self::getHttpCodeForException($exception);
        $message = self::getMessageForException($exception, $httpCode);
        $errors = self::getErrorsForException($exception);

        // 返回JSON响应
        ApiResponse::error($message, $httpCode, $errors);
    }

    public static function handleError($errno, $errstr, $errfile, $errline) {
        // 将错误转换为异常
        if (error_reporting() & $errno) {
            throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
        }

        return false;
    }

    private static function logException($exception) {
        $log = sprintf(
            "[%s] API异常: %s in %s on line %d\n堆栈:\n%s\n",
            date('Y-m-d H:i:s'),
            $exception->getMessage(),
            $exception->getFile(),
            $exception->getLine(),
            $exception->getTraceAsString()
        );

        file_put_contents('logs/api_errors.log', $log, FILE_APPEND);
    }

    private static function getHttpCodeForException($exception) {
        $exceptionClass = get_class($exception);

        switch ($exceptionClass) {
            case 'InvalidArgumentException':
            case 'TypeError':
                return 400; // Bad Request

            case 'PDOException':
            case 'DatabaseException':
                return 503; // Service Unavailable

            case 'UnauthorizedException':
                return 401; // Unauthorized

            case 'ForbiddenException':
                return 403; // Forbidden

            case 'NotFoundException':
                return 404; // Not Found

            case 'ValidationException':
                return 422; // Unprocessable Entity

            default:
                return 500; // Internal Server Error
        }
    }

    private static function getMessageForException($exception, $httpCode) {
        $exceptionClass = get_class($exception);

        // 生产环境:返回通用消息
        if (getenv('APP_ENV') === 'production') {
            switch ($httpCode) {
                case 400:
                    return '请求参数错误';
                case 401:
                    return '未授权访问';
                case 403:
                    return '禁止访问';
                case 404:
                    return '资源不存在';
                case 422:
                    return '数据验证失败';
                case 503:
                    return '服务暂时不可用';
                default:
                    return '服务器内部错误';
            }
        }

        // 开发环境:返回详细消息
        return $exception->getMessage();
    }

    private static function getErrorsForException($exception) {
        if (method_exists($exception, 'getErrors')) {
            return $exception->getErrors();
        }

        return null;
    }
}

// 自定义异常类
class UnauthorizedException extends Exception {}
class ForbiddenException extends Exception {}
class NotFoundException extends Exception {}

// 使用示例
echo "<h4>场景1:API异常处理</h4>";

// 注册API异常处理器
ApiExceptionHandler::register();

// 模拟API请求
if (isset($_GET['action'])) {
    switch ($_GET['action']) {
        case 'auth':
            throw new UnauthorizedException("用户未登录");

        case 'validate':
            throw new ValidationException("数据验证失败", [
                'email' => '邮箱格式不正确',
                'password' => '密码长度至少6位'
            ]);

        case 'database':
            throw new PDOException("数据库连接失败");

        case 'notfound':
            throw new NotFoundException("用户不存在");

        default:
            // 这个异常会被异常处理器捕获
            throw new Exception("未知操作");
    }
}

echo "<div class='alert alert-info'>";
echo "API异常处理器已注册,异常将返回JSON格式的响应";
echo "<ul>";
echo "<li><a href='?action=auth'>测试401未授权异常</a></li>";
echo "<li><a href='?action=validate'>测试422验证异常</a></li>";
echo "<li><a href='?action=database'>测试503数据库异常</a></li>";
echo "<li><a href='?action=notfound'>测试404未找到异常</a></li>";
echo "<li><a href='?action=other'>测试500服务器错误</a></li>";
echo "</ul>";
echo "</div>";

示例6:异常处理器的高级用法

演示异常处理器的高级用法,包括嵌套异常处理和恢复:

<?php
// 异常处理器管理器
class ExceptionHandlerManager {
    private static $handlers = [];
    private static $originalHandler = null;

    /**
     * 推入新的异常处理器
     */
    public static function push($handler) {
        if (empty(self::$handlers)) {
            self::$originalHandler = set_exception_handler($handler);
        } else {
            set_exception_handler($handler);
        }

        self::$handlers[] = $handler;
        return count(self::$handlers);
    }

    /**
     * 弹出当前异常处理器
     */
    public static function pop() {
        if (empty(self::$handlers)) {
            return false;
        }

        array_pop(self::$handlers);
        restore_exception_handler();

        return true;
    }

    /**
     * 使用临时异常处理器执行代码
     */
    public static function with($handler, $callback) {
        $previousCount = count(self::$handlers);

        try {
            self::push($handler);
            $result = $callback();
            return $result;
        } finally {
            // 恢复到之前的状态
            while (count(self::$handlers) > $previousCount) {
                self::pop();
            }
        }
    }

    /**
     * 获取当前处理器栈
     */
    public static function getStack() {
        return self::$handlers;
    }

    /**
     * 清空所有处理器
     */
    public static function clear() {
        while (!empty(self::$handlers)) {
            self::pop();
        }
        return true;
    }
}

// 不同类型的异常处理器
$logHandler = function($exception) {
    error_log("[日志处理器] " . $exception->getMessage());
    // 传递给下一个处理器
    throw $exception;
};

$notifyHandler = function($exception) {
    // 发送通知
    error_log("[通知处理器] 发送异常通知: " . $exception->getMessage());
    // 传递给下一个处理器
    throw $exception;
};

$displayHandler = function($exception) {
    echo "<div style='background:#f8d7da; padding:10px; border:1px solid #f5c6cb;'>";
    echo "<strong>[显示处理器]</strong> 异常: " . htmlspecialchars($exception->getMessage());
    echo "</div>";
    // 不重新抛出,终止处理链
};

$apiHandler = function($exception) {
    header('Content-Type: application/json');
    echo json_encode([
        'error' => $exception->getMessage(),
        'code' => $exception->getCode(),
    ]);
    exit;
};

// 使用示例
echo "<h4>使用ExceptionHandlerManager管理异常处理器:</h4>";

// 1. 推入多个处理器
ExceptionHandlerManager::push($logHandler);
ExceptionHandlerManager::push($notifyHandler);
ExceptionHandlerManager::push($displayHandler);

echo "处理器栈大小: " . count(ExceptionHandlerManager::getStack()) . "<br>";

// 2. 使用临时处理器
echo "<h5>使用临时异常处理器:</h5>";

$result = ExceptionHandlerManager::with($apiHandler, function() {
    echo "在执行临时处理器中的代码<br>";
    // 这个异常会被$apiHandler处理
    throw new Exception("API异常测试");
});

echo "临时处理器执行结果: " . ($result ?? 'null') . "<br>";

echo "<h5>测试当前处理器栈:</h5>";
// 这个异常会被$displayHandler处理
throw new Exception("测试异常处理链");

echo "<div class='alert alert-success'>";
echo "异常处理器管理器演示完成";
echo "</div>";

// 清理
ExceptionHandlerManager::clear();

常见使用场景

自定义异常日志

记录异常到自定义格式的日志文件或数据库中,便于后续分析和监控。

异常页面定制

显示用户友好的异常页面,而不是PHP的默认错误信息,提升用户体验。

异常通知

当发生未捕获的异常时,自动发送邮件、短信或Slack通知给开发团队。

注意事项

  • 脚本终止:异常处理器执行后,脚本通常会终止执行,除非在异常处理器中明确继续执行。
  • 异常类型:在PHP 7+中,异常处理器可以捕获Throwable接口的实现,包括ExceptionError
  • 嵌套设置:多次调用set_exception_handler()会创建异常处理器栈,可以使用restore_exception_handler()恢复上一个处理器。
  • 与错误处理配合:异常处理器通常与错误处理器(set_error_handler())配合使用,错误处理器可以将错误转换为异常。
  • 性能考虑:在生产环境中,异常处理器应尽量减少输出,通常只记录日志并显示用户友好的错误页面。
  • HTTP状态码:在Web应用中,异常处理器应该设置适当的HTTP状态码(如500内部服务器错误)。
  • 资源清理:异常处理器中应该考虑资源清理,如关闭数据库连接、释放文件锁等。

最佳实践

环境感知的异常处理

根据不同的环境(开发、测试、生产)使用不同的异常处理策略:

function setupExceptionHandling() {
    $env = getenv('APP_ENV');

    if ($env === 'development') {
        // 开发环境:显示详细异常
        set_exception_handler('devExceptionHandler');
    } else {
        // 生产环境:记录日志但不显示详情
        set_exception_handler('prodExceptionHandler');
    }
}
结构化异常日志

记录结构化的异常信息,便于日志分析:

function logException($exception) {
    $log = json_encode([
        'timestamp' => date('c'),
        'exception' => get_class($exception),
        'message' => $exception->getMessage(),
        'file' => $exception->getFile(),
        'line' => $exception->getLine(),
        'trace' => $exception->getTrace(),
        'request' => $_SERVER['REQUEST_URI'] ?? null,
        'ip' => $_SERVER['REMOTE_ADDR'] ?? null
    ], JSON_PRETTY_PRINT);

    file_put_contents('exceptions.log', $log . "\n", FILE_APPEND);
}
统一的异常处理入口

创建统一的异常处理入口,管理所有异常处理逻辑:

class ExceptionManager {
    public static function init() {
        set_exception_handler([self::class, 'handleException']);
        set_error_handler([self::class, 'handleError']);
    }

    public static function handleException($exception) {
        // 统一异常处理逻辑
        self::log($exception);
        self::notify($exception);
        self::display($exception);
    }
}
异常处理器测试

编写测试来验证异常处理器的行为:

function testExceptionHandler() {
    set_exception_handler('testHandler');

    // 触发异常
    try {
        throw new Exception('测试异常');
    } catch (Exception $e) {
        // 这个异常被捕获,不会触发异常处理器
    }

    // 这个异常会被异常处理器处理
    throw new Exception('未捕获异常');

    restore_exception_handler();
}