PHPset_error_handler()函数

简介

set_error_handler() 是PHP内置的错误处理函数,用于设置用户自定义的错误处理函数。通过这个函数,开发者可以定义自己的错误处理逻辑,替代PHP的默认错误处理机制,从而实现更灵活的错误处理、日志记录和调试。

这个函数允许你接管PHP的错误处理流程,可以用于记录错误日志、显示友好的错误页面、发送错误通知等。自定义错误处理器可以处理除了E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING之外的所有错误类型。
注意:自定义错误处理器不能处理致命错误(Fatal Errors)。要处理致命错误,需要使用register_shutdown_function()配合error_get_last()

语法

mixed set_error_handler(callable $error_handler[, int $error_types = E_ALL | E_STRICT])

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

参数说明

参数 类型 默认值 描述
$error_handler callable 必填 自定义的错误处理函数。可以是一个函数名、类方法数组或匿名函数。这个函数需要接受5个参数:
1. $errno - 错误级别
2. $errstr - 错误信息
3. $errfile - 发生错误的文件名
4. $errline - 发生错误的行号
5. $errcontext - 错误发生时活动符号表的数组(PHP 7.2.0+已弃用)
$error_types int E_ALL | E_STRICT 指定哪些错误类型应该被这个错误处理器处理。可以是错误级别常量的位掩码组合。如果没有设置,默认处理所有错误类型。

返回值

mixed - 返回之前定义的错误处理程序,或者在出错时返回null

可能的返回值:

  • callable - 之前设置的自定义错误处理器
  • null - 如果之前没有设置自定义错误处理器,或者发生错误
  • false - 如果提供的错误处理器无效

示例

示例1:基本用法

演示如何使用set_error_handler()设置基本的自定义错误处理器:

<?php
// 自定义错误处理函数
function customErrorHandler($errno, $errstr, $errfile, $errline) {
    echo "<div style='background:#f8d7da; padding:10px; margin:5px; border:1px solid #f5c6cb;'>";
    echo "<strong>自定义错误处理器捕获到错误:</strong><br>";
    echo "错误级别: " . $errno . "<br>";
    echo "错误信息: " . htmlspecialchars($errstr) . "<br>";
    echo "错误文件: " . $errfile . "<br>";
    echo "错误行号: " . $errline;
    echo "</div>";

    // 返回true表示错误已处理,阻止PHP执行默认错误处理器
    return true;
}

// 设置自定义错误处理器
$previousHandler = set_error_handler("customErrorHandler");

echo "<h4>测试错误处理:</h4>";

// 触发不同类型的错误
echo $undefinedVariable;  // 注意:使用未定义的变量(E_NOTICE)

$result = 10 / 0;  // 警告:除以零(E_WARNING)

// 恢复之前的错误处理器
restore_error_handler();

echo "<div class='alert alert-info'>";
echo "已恢复之前的错误处理器";
echo "</div>";

示例2:使用匿名函数

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

<?php
// 使用匿名函数作为错误处理器
$errorHandler = function($errno, $errstr, $errfile, $errline) {
    $errorTypes = [
        1 => 'E_ERROR',
        2 => 'E_WARNING',
        8 => 'E_NOTICE',
        256 => 'E_USER_ERROR',
        512 => 'E_USER_WARNING',
        1024 => 'E_USER_NOTICE',
        4096 => 'E_RECOVERABLE_ERROR',
        8192 => 'E_DEPRECATED',
        16384 => 'E_USER_DEPRECATED',
    ];

    $errorName = isset($errorTypes[$errno]) ? $errorTypes[$errno] : "未知错误($errno)";

    $log = sprintf(
        "[%s] [%s] %s in %s on line %d\n",
        date('Y-m-d H:i:s'),
        $errorName,
        $errstr,
        $errfile,
        $errline
    );

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

    // 如果是开发环境,也显示错误
    if (getenv('APP_ENV') === 'development') {
        echo "<div style='background:#fff3cd; padding:10px; margin:5px; border:1px solid #ffeaa7;'>";
        echo "<strong>错误:</strong> " . htmlspecialchars($errstr) . "<br>";
        echo "<strong>位置:</strong> " . $errfile . " 第 " . $errline . " 行";
        echo "</div>";
    }

    return true; // 阻止PHP默认错误处理器
};

// 设置错误处理器
set_error_handler($errorHandler);

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

// 触发错误
strpos();  // 警告:缺少参数

// 查看错误日志
if (file_exists('error.log')) {
    echo "<div class='alert alert-success'>";
    echo "错误已记录到日志文件";
    echo "</div>";
}

示例3:在类中使用

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

<?php
// 错误处理类
class ErrorHandler {
    private $logFile = 'app_errors.log';

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

        // 设置错误处理器
        set_error_handler([$this, 'handleError']);
    }

    public function handleError($errno, $errstr, $errfile, $errline) {
        // 构建错误信息
        $errorInfo = [
            'timestamp' => date('Y-m-d H:i:s'),
            'errno' => $errno,
            'errstr' => $errstr,
            'errfile' => $errfile,
            'errline' => $errline,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
        ];

        // 记录到日志
        $this->logError($errorInfo);

        // 根据错误级别处理
        switch ($errno) {
            case E_USER_ERROR:
            case E_RECOVERABLE_ERROR:
                $this->handleFatalError($errorInfo);
                break;

            case E_USER_WARNING:
            case E_WARNING:
                $this->handleWarning($errorInfo);
                break;

            case E_USER_NOTICE:
            case E_NOTICE:
            case E_DEPRECATED:
            case E_USER_DEPRECATED:
                $this->handleNotice($errorInfo);
                break;

            default:
                $this->handleUnknownError($errorInfo);
        }

        return true; // 阻止PHP默认处理器
    }

    private function logError($errorInfo) {
        $logEntry = sprintf(
            "[%s] [Level:%d] %s in %s:%d (IP:%s, URI:%s)\n",
            $errorInfo['timestamp'],
            $errorInfo['errno'],
            $errorInfo['errstr'],
            $errorInfo['errfile'],
            $errorInfo['errline'],
            $errorInfo['ip'],
            $errorInfo['uri']
        );

        file_put_contents($this->logFile, $logEntry, FILE_APPEND);
    }

    private function handleFatalError($errorInfo) {
        // 发送邮件通知等
        error_log("严重错误: " . $errorInfo['errstr']);

        // 显示用户友好的错误页面
        if (php_sapi_name() !== 'cli') {
            echo "<div style='text-align:center; padding:50px;'>";
            echo "<h1 style='color:#dc3545;'>系统错误</h1>";
            echo "<p>抱歉,系统发生了内部错误。我们的技术团队已收到通知。</p>";
            echo "</div>";
        }
    }

    private function handleWarning($errorInfo) {
        // 开发环境显示警告
        if (getenv('APP_ENV') === 'development') {
            echo "<div style='background:#fff3cd; padding:10px; margin:5px; border:1px solid #ffeaa7;'>";
            echo "<strong>警告:</strong> " . htmlspecialchars($errorInfo['errstr']);
            echo "</div>";
        }
    }

    private function handleNotice($errorInfo) {
        // 开发环境显示通知
        if (getenv('APP_ENV') === 'development') {
            echo "<div style='background:#e7f3ff; padding:10px; margin:5px; border:1px solid #b3d7ff;'>";
            echo "<strong>注意:</strong> " . htmlspecialchars($errorInfo['errstr']);
            echo "</div>";
        }
    }

    private function handleUnknownError($errorInfo) {
        // 处理未知错误类型
        error_log("未知错误: " . $errorInfo['errstr']);
    }

    public function __destruct() {
        // 恢复错误处理器
        restore_error_handler();
    }
}

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

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

$handler = new ErrorHandler();

// 触发不同类型的错误
trigger_error("用户自定义错误", E_USER_ERROR);
trigger_error("用户自定义警告", E_USER_WARNING);
trigger_error("用户自定义通知", E_USER_NOTICE);

echo "<div class='alert alert-info'>";
echo "查看 app_errors.log 文件获取详细错误日志";
echo "</div>";

示例4:错误级别过滤

演示如何使用$error_types参数过滤错误级别:

<?php
// 只处理错误和警告的处理器
function errorAndWarningHandler($errno, $errstr, $errfile, $errline) {
    echo "<div style='background:#f8d7da; padding:10px; margin:5px; border:1px solid #f5c6cb;'>";
    echo "<strong>错误/警告处理器:</strong> " . htmlspecialchars($errstr);
    echo "</div>";
    return true;
}

// 只处理通知的处理器
function noticeHandler($errno, $errstr, $errfile, $errline) {
    echo "<div style='background:#e7f3ff; padding:10px; margin:5px; border:1px solid #b3d7ff;'>";
    echo "<strong>通知处理器:</strong> " . htmlspecialchars($errstr);
    echo "</div>";
    return true;
}

// 只处理用户错误的处理器
function userErrorHandler($errno, $errstr, $errfile, $errline) {
    echo "<div style='background:#d4edda; padding:10px; margin:5px; border:1px solid #c3e6cb;'>";
    echo "<strong>用户错误处理器:</strong> " . htmlspecialchars($errstr);
    echo "</div>";
    return true;
}

echo "<h4>测试错误级别过滤:</h4>";

// 1. 只处理错误和警告
set_error_handler("errorAndWarningHandler", E_ERROR | E_WARNING | E_USER_ERROR | E_USER_WARNING);
echo "<p>1. 当前处理器: 只处理错误和警告</p>";
trigger_error("用户警告测试", E_USER_WARNING);  // 会被处理
trigger_error("用户通知测试", E_USER_NOTICE);   // 不会被处理(PHP默认处理器处理)

// 2. 只处理通知
set_error_handler("noticeHandler", E_NOTICE | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED);
echo "<p>2. 当前处理器: 只处理通知</p>";
trigger_error("用户通知测试2", E_USER_NOTICE);  // 会被处理
trigger_error("用户警告测试2", E_USER_WARNING); // 不会被处理

// 3. 恢复默认处理器
restore_error_handler();
echo "<p>3. 当前处理器: PHP默认处理器</p>";
trigger_error("用户错误测试", E_USER_ERROR);    // PHP默认处理器处理

// 4. 复杂的错误级别组合
echo "<h4>复杂的错误级别组合:</h4>";

// 处理除弃用警告外的所有错误
set_error_handler("errorAndWarningHandler", E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);

// 或者:处理所有错误,但排除通知
set_error_handler("errorAndWarningHandler", E_ALL & ~E_NOTICE & ~E_USER_NOTICE);

echo "<div class='alert alert-info'>";
echo "可以使用位运算符组合错误级别:";
echo "<ul>";
echo "<li>E_ALL | E_STRICT - 包含所有错误</li>";
echo "<li>E_ALL & ~E_DEPRECATED - 排除弃用警告</li>";
echo "<li>E_ERROR | E_WARNING | E_PARSE - 只处理错误、警告和解析错误</li>";
echo "</ul>";
echo "</div>";

示例5:完整的错误处理系统

创建一个完整的错误处理系统,包括错误处理、异常处理和致命错误处理:

<?php
/**
 * 完整的错误处理系统
 */
class AppErrorHandler {
    private static $instance = null;
    private $logFile = 'logs/app.log';
    private $errorLogFile = 'logs/errors.log';
    private $environment = 'production';

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

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

        // 设置错误处理器
        $this->setupErrorHandling();
    }

    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 setupErrorHandling() {
        // 设置错误报告级别
        if ($this->environment === 'development') {
            error_reporting(E_ALL);
            ini_set('display_errors', 0); // 我们自定义显示
        } else {
            error_reporting(E_ERROR | E_WARNING | E_PARSE);
            ini_set('display_errors', 0);
        }

        // 设置自定义错误处理器
        set_error_handler([$this, 'handleError']);

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

        // 设置关闭函数处理致命错误
        register_shutdown_function([$this, 'handleShutdown']);
    }

    public function handleError($errno, $errstr, $errfile, $errline) {
        $error = [
            'type' => 'ERROR',
            'level' => $this->getErrorLevelName($errno),
            'message' => $errstr,
            'file' => $errfile,
            'line' => $errline,
            'timestamp' => date('Y-m-d H:i:s'),
            'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5),
        ];

        // 记录错误
        $this->logError($error);

        // 处理错误
        $this->processError($error);

        // 如果是致命错误,不要返回true(让PHP处理)
        if (in_array($errno, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            return false;
        }

        return true; // 阻止PHP默认处理器
    }

    public function handleException($exception) {
        $error = [
            'type' => 'EXCEPTION',
            'level' => 'EXCEPTION',
            'message' => $exception->getMessage(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'timestamp' => date('Y-m-d H:i:s'),
            'backtrace' => $exception->getTrace(),
        ];

        // 记录异常
        $this->logError($error);

        // 处理异常
        $this->processError($error, $exception);

        // 如果是控制台模式,输出错误
        if (php_sapi_name() === 'cli') {
            fwrite(STDERR, "未捕获异常: " . $exception->getMessage() . "\n");
        }
    }

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

        if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            $fatalError = [
                'type' => 'FATAL',
                'level' => 'FATAL',
                'message' => $error['message'],
                'file' => $error['file'],
                'line' => $error['line'],
                'timestamp' => date('Y-m-d H:i:s'),
            ];

            // 记录致命错误
            $this->logError($fatalError);

            // 处理致命错误
            $this->processFatalError($fatalError);
        }
    }

    private function getErrorLevelName($errno) {
        $levels = [
            1 => 'E_ERROR',
            2 => 'E_WARNING',
            4 => 'E_PARSE',
            8 => 'E_NOTICE',
            16 => 'E_CORE_ERROR',
            32 => 'E_CORE_WARNING',
            64 => 'E_COMPILE_ERROR',
            128 => 'E_COMPILE_WARNING',
            256 => 'E_USER_ERROR',
            512 => 'E_USER_WARNING',
            1024 => 'E_USER_NOTICE',
            2048 => 'E_STRICT',
            4096 => 'E_RECOVERABLE_ERROR',
            8192 => 'E_DEPRECATED',
            16384 => 'E_USER_DEPRECATED',
        ];

        return isset($levels[$errno]) ? $levels[$errno] : "UNKNOWN($errno)";
    }

    private function logError($error) {
        $logEntry = sprintf(
            "[%s] [%s] [%s] %s in %s on line %d\n",
            $error['timestamp'],
            $error['type'],
            $error['level'],
            $error['message'],
            $error['file'],
            $error['line']
        );

        // 记录到错误日志
        file_put_contents($this->errorLogFile, $logEntry, FILE_APPEND);

        // 记录到应用日志
        if ($this->environment === 'development') {
            $fullLog = $logEntry;
            if (isset($error['backtrace'])) {
                $fullLog .= "Backtrace:\n";
                foreach ($error['backtrace'] as $i => $trace) {
                    $fullLog .= sprintf("#%d %s:%d\n", $i,
                        $trace['file'] ?? 'unknown',
                        $trace['line'] ?? 0
                    );
                }
            }
            file_put_contents($this->logFile, $fullLog, FILE_APPEND);
        }
    }

    private function processError($error, $exception = null) {
        // 根据环境处理错误
        if ($this->environment === 'development') {
            $this->displayDebugError($error, $exception);
        } else {
            $this->displayUserFriendlyError($error);
        }

        // 发送通知(邮件、Slack等)
        if (in_array($error['level'], ['E_ERROR', 'E_USER_ERROR', 'EXCEPTION', 'FATAL'])) {
            $this->sendErrorNotification($error);
        }
    }

    private function processFatalError($error) {
        if (php_sapi_name() !== 'cli' && $this->environment === 'production') {
            // 显示用户友好的错误页面
            $this->displayFatalErrorPage();
        }
    }

    private function displayDebugError($error, $exception = null) {
        if (php_sapi_name() !== 'cli') {
            echo "<div style='background:#f8f9fa; padding:15px; margin:10px; border:1px solid #ddd; font-family:monospace;'>";
            echo "<h3 style='color:#dc3545; margin-top:0;'>错误详情</h3>";
            echo "<table style='width:100%; border-collapse:collapse;'>";
            echo "<tr><td style='width:100px; padding:5px; font-weight:bold;'>类型:</td><td style='padding:5px;'>" . $error['type'] . "</td></tr>";
            echo "<tr><td style='padding:5px; font-weight:bold;'>级别:</td><td style='padding:5px;'>" . $error['level'] . "</td></tr>";
            echo "<tr><td style='padding:5px; font-weight:bold;'>消息:</td><td style='padding:5px;'>" . htmlspecialchars($error['message']) . "</td></tr>";
            echo "<tr><td style='padding:5px; font-weight:bold;'>文件:</td><td style='padding:5px;'>" . $error['file'] . "</td></tr>";
            echo "<tr><td style='padding:5px; font-weight:bold;'>行号:</td><td style='padding:5px;'>" . $error['line'] . "</td></tr>";
            echo "<tr><td style='padding:5px; font-weight:bold;'>时间:</td><td style='padding:5px;'>" . $error['timestamp'] . "</td></tr>";
            echo "</table>";

            if ($exception instanceof Exception) {
                echo "<h4>异常堆栈:</h4>";
                echo "<pre style='background:#e9ecef; padding:10px; border:1px solid #ddd; overflow:auto;'>";
                echo htmlspecialchars($exception->getTraceAsString());
                echo "</pre>";
            }

            if (isset($error['backtrace'])) {
                echo "<h4>调用堆栈:</h4>";
                echo "<pre style='background:#e9ecef; padding:10px; border:1px solid #ddd; overflow:auto;'>";
                foreach ($error['backtrace'] as $i => $trace) {
                    echo "#" . $i . " ";
                    if (isset($trace['file'])) {
                        echo $trace['file'] . "(" . ($trace['line'] ?? 0) . "): ";
                    }
                    if (isset($trace['class'])) {
                        echo $trace['class'] . $trace['type'];
                    }
                    if (isset($trace['function'])) {
                        echo $trace['function'] . "()";
                    }
                    echo "\n";
                }
                echo "</pre>";
            }

            echo "</div>";
        }
    }

    private function displayUserFriendlyError($error) {
        if (php_sapi_name() !== 'cli') {
            // 显示用户友好的错误信息
            if (in_array($error['level'], ['E_ERROR', 'E_USER_ERROR', 'EXCEPTION', 'FATAL'])) {
                echo "<div style='text-align:center; padding:50px;'>";
                echo "<h1 style='color:#dc3545;'>系统错误</h1>";
                echo "<p>抱歉,系统发生了内部错误。我们的技术团队已收到通知。</p>";
                echo "<p><a href='/'>返回首页</a></p>";
                echo "</div>";
            }
        }
    }

    private function displayFatalErrorPage() {
        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 sendErrorNotification($error) {
        // 这里可以发送邮件、Slack通知等
        // 示例:发送到错误监控服务
        $notification = sprintf(
            "[%s] 严重错误: %s in %s:%d",
            $error['timestamp'],
            $error['message'],
            $error['file'],
            $error['line']
        );

        error_log($notification);
    }
}

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

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

// 初始化错误处理系统
AppErrorHandler::getInstance();

// 测试不同类型的错误
echo "<h5>1. 测试用户错误:</h5>";
trigger_error("这是一个用户自定义错误", E_USER_ERROR);

echo "<h5>2. 测试警告:</h5>";
trigger_error("这是一个用户自定义警告", E_USER_WARNING);

echo "<h5>3. 测试异常:</h5>";
try {
    throw new Exception("这是一个未捕获的异常");
} catch (Exception $e) {
    // 这里不会捕获,因为会被异常处理器处理
}

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

示例6:错误处理器的实际应用

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

<?php
// 数据库操作类
class Database {
    private $connection;
    private static $errorHandlerSet = false;

    public function __construct($host, $username, $password, $database) {
        // 设置数据库错误处理器
        if (!self::$errorHandlerSet) {
            $this->setDatabaseErrorHandler();
            self::$errorHandlerSet = true;
        }

        // 连接数据库
        $this->connection = mysqli_connect($host, $username, $password, $database);

        if (!$this->connection) {
            throw new Exception("数据库连接失败: " . mysqli_connect_error());
        }
    }

    private function setDatabaseErrorHandler() {
        set_error_handler(function($errno, $errstr, $errfile, $errline) {
            // 只处理数据库相关的错误
            if (strpos($errstr, 'mysqli') !== false ||
                strpos($errstr, 'mysql') !== false) {

                // 记录数据库错误
                $log = sprintf(
                    "[%s] 数据库错误: %s in %s:%d\n",
                    date('Y-m-d H:i:s'),
                    $errstr,
                    $errfile,
                    $errline
                );

                file_put_contents('database_errors.log', $log, FILE_APPEND);

                // 抛出异常,让上层代码处理
                throw new Exception("数据库操作失败: " . $errstr);
            }

            // 非数据库错误传递给下一个处理器
            return false;
        }, E_WARNING | E_NOTICE);
    }

    public function query($sql) {
        $result = mysqli_query($this->connection, $sql);

        if (!$result) {
            throw new Exception("查询失败: " . mysqli_error($this->connection));
        }

        return $result;
    }

    public function __destruct() {
        if ($this->connection) {
            mysqli_close($this->connection);
        }

        // 恢复错误处理器
        if (self::$errorHandlerSet) {
            restore_error_handler();
            self::$errorHandlerSet = false;
        }
    }
}

// API响应类
class ApiResponse {
    public static function error($message, $code = 500, $data = []) {
        $response = [
            'success' => false,
            'error' => [
                'code' => $code,
                'message' => $message,
                'timestamp' => date('c'),
            ],
            'data' => $data,
        ];

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

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

    public static function handleError($errno, $errstr, $errfile, $errline) {
        // 记录错误
        error_log("API错误: " . $errstr);

        // 返回JSON错误响应
        ApiResponse::error("服务器内部错误", 500, [
            'error_type' => 'PHP_ERROR',
            'error_message' => $errstr,
        ]);

        return true;
    }

    public static function handleException($exception) {
        // 记录异常
        error_log("API异常: " . $exception->getMessage());

        // 返回JSON错误响应
        ApiResponse::error("服务器内部错误", 500, [
            'error_type' => 'EXCEPTION',
            'error_message' => $exception->getMessage(),
        ]);
    }
}

// 使用示例
echo "<h4>场景1:数据库错误处理</h4>";

try {
    // 模拟数据库连接
    $db = new Database('localhost', 'wrong_user', 'wrong_pass', 'nonexistent_db');
} catch (Exception $e) {
    echo "<div class='alert alert-danger'>";
    echo "数据库错误: " . $e->getMessage();
    echo "</div>";
}

echo "<h4>场景2:API错误处理</h4>";

// 注册API错误处理器
ApiErrorHandler::register();

// 模拟API错误
if (isset($_GET['test_error'])) {
    // 这个错误会被ApiErrorHandler处理
    trigger_error("测试API错误处理");
}

echo "<div class='alert alert-info'>";
echo "API错误处理器已注册,错误将返回JSON格式的响应";
echo "</div>";

常见使用场景

自定义错误日志

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

错误页面定制

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

错误通知

当发生严重错误时,自动发送邮件、短信或Slack通知给开发团队。

注意事项

  • 致命错误:set_error_handler()不能处理致命错误(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_COMPILE_WARNING)。需要使用register_shutdown_function()配合error_get_last()来处理。
  • @操作符:使用@错误抑制操作符时,错误会被抑制,不会传递给自定义错误处理器。
  • 处理器返回值:错误处理器返回true表示错误已被处理,PHP不会执行默认的错误处理器。返回false会继续传递给下一个错误处理器。
  • 嵌套设置:多次调用set_error_handler()会创建错误处理器栈,可以使用restore_error_handler()恢复上一个处理器。
  • 性能考虑:自定义错误处理器可能会影响性能,特别是在处理大量小错误时。在生产环境中应谨慎使用。
  • 错误上下文:错误处理器的第五个参数$errcontext在PHP 7.2.0中已弃用,不推荐使用。
  • 与异常处理配合:错误处理器和异常处理器(set_exception_handler())应该协同工作,提供完整的错误处理解决方案。

最佳实践

环境感知的错误处理

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

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

    if ($env === 'development') {
        // 开发环境:显示详细错误
        error_reporting(E_ALL);
        set_error_handler('devErrorHandler');
    } else {
        // 生产环境:记录日志但不显示
        error_reporting(E_ERROR | E_WARNING);
        set_error_handler('prodErrorHandler');
    }
}
结构化错误日志

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

function logError($errno, $errstr, $errfile, $errline) {
    $log = json_encode([
        'timestamp' => date('c'),
        'level' => $errno,
        'message' => $errstr,
        'file' => $errfile,
        'line' => $errline,
        'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)
    ], JSON_PRETTY_PRINT);

    file_put_contents('error.log', $log . "\n", FILE_APPEND);
}
统一的错误处理入口

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

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

    public static function handleError($errno, $errstr, $errfile, $errline) {
        // 统一错误处理逻辑
    }
}
错误处理器测试

编写测试来验证错误处理器的行为:

function testErrorHandler() {
    set_error_handler('testHandler');

    // 触发不同类型的错误
    trigger_error('Test error', E_USER_ERROR);
    trigger_error('Test warning', E_USER_WARNING);

    // 验证错误被正确处理
    assert(file_exists('test.log'));

    restore_error_handler();
}