set_exception_handler() 是PHP内置的异常处理函数,用于设置用户自定义的异常处理函数。通过这个函数,开发者可以定义自己的异常处理逻辑,捕获未被try/catch块捕获的异常,从而实现更优雅的异常处理和错误恢复。
throw语句抛出的异常和某些类型的错误转换而来的异常。
callable|null set_exception_handler(callable $exception_handler)
函数返回之前定义的异常处理程序,或者在出错时返回null。
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| $exception_handler | callable | 必填 |
自定义的异常处理函数。可以是一个函数名、类方法数组或匿名函数。这个函数需要接受1个参数:
|
callable|null - 返回之前定义的异常处理程序,或者在出错时返回null。
可能的返回值:
演示如何使用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();
演示如何使用匿名函数作为异常处理器:
<?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>";
演示如何在类中使用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>";
创建一个完整的异常处理系统,包括不同类型异常的处理:
<?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>";
演示在实际应用场景中如何使用异常处理器:
<?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>";
演示异常处理器的高级用法,包括嵌套异常处理和恢复:
<?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通知给开发团队。
Throwable接口的实现,包括Exception和Error。set_exception_handler()会创建异常处理器栈,可以使用restore_exception_handler()恢复上一个处理器。set_error_handler())配合使用,错误处理器可以将错误转换为异常。根据不同的环境(开发、测试、生产)使用不同的异常处理策略:
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();
}