Skip to content

错误处理

概述

正确的错误处理对于构建健壮的PHP应用程序至关重要。本章涵盖PHP的错误处理机制、异常、自定义错误处理器、日志记录、调试技术以及在生产环境中管理错误的最佳实践。

PHP错误类型

理解错误级别

php
<?php
// 为学习目的显示所有错误类型
error_reporting(E_ALL);
ini_set('display_errors', 1);

// 不同类型的错误

// 致命错误 - 停止脚本执行
// function_that_does_not_exist(); // 致命错误:调用未定义的函数

// 解析错误 - 语法错误
// echo "Hello World" // 解析错误:语法错误,意外的文件结束

// 警告 - 脚本继续执行
$file = fopen('nonexistent_file.txt', 'r'); // 警告:fopen():没有这样的文件或目录

// 注意 - 脚本继续执行
echo $undefined_variable; // 注意:未定义的变量

// 严格标准 - 代码建议
// class MyClass {
//     function myMethod() {} // 应该是public、protected或private
// }

// 用户生成的错误
trigger_error("这是一个用户错误", E_USER_ERROR);
trigger_error("这是一个用户警告", E_USER_WARNING);
trigger_error("这是一个用户通知", E_USER_NOTICE);

// 错误常量
echo "E_ERROR: " . E_ERROR . "\n";           // 1
echo "E_WARNING: " . E_WARNING . "\n";       // 2
echo "E_PARSE: " . E_PARSE . "\n";           // 4
echo "E_NOTICE: " . E_NOTICE . "\n";         // 8
echo "E_STRICT: " . E_STRICT . "\n";         // 2048
echo "E_ALL: " . E_ALL . "\n";               // 所有错误
?>

错误报告配置

php
<?php
// 开发环境 - 显示所有错误
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);

// 生产环境 - 向用户隐藏错误
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/path/to/error.log');

// 自定义错误报告级别
error_reporting(E_ERROR | E_WARNING | E_PARSE); // 仅显示关键错误
error_reporting(E_ALL & ~E_NOTICE);             // 除了通知以外的所有错误
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT); // 除了通知和严格模式以外的所有错误

// 检查当前错误报告级别
$currentLevel = error_reporting();
echo "当前错误报告级别:$currentLevel\n";

// 临时禁用错误报告
$oldLevel = error_reporting(0);
// 可能生成错误的代码
error_reporting($oldLevel); // 恢复之前的级别

// 获取错误级别名称的函数
function getErrorLevelName($level) {
    $levels = [
        E_ERROR => 'E_ERROR',
        E_WARNING => 'E_WARNING',
        E_PARSE => 'E_PARSE',
        E_NOTICE => 'E_NOTICE',
        E_CORE_ERROR => 'E_CORE_ERROR',
        E_CORE_WARNING => 'E_CORE_WARNING',
        E_COMPILE_ERROR => 'E_COMPILE_ERROR',
        E_COMPILE_WARNING => 'E_COMPILE_WARNING',
        E_USER_ERROR => 'E_USER_ERROR',
        E_USER_WARNING => 'E_USER_WARNING',
        E_USER_NOTICE => 'E_USER_NOTICE',
        E_STRICT => 'E_STRICT',
        E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
        E_DEPRECATED => 'E_DEPRECATED',
        E_USER_DEPRECATED => 'E_USER_DEPRECATED'
    ];
    
    return $levels[$level] ?? 'UNKNOWN';
}
?>

异常处理

基本Try-Catch块

php
<?php
// 基本异常处理
try {
    $result = 10 / 0; // 这在PHP中是可以的(返回INF)
    echo "结果:$result\n";
    
    // 抛出自定义异常
    throw new Exception("出现了一些问题!");
    
} catch (Exception $e) {
    echo "捕获到异常:" . $e->getMessage() . "\n";
    echo "文件:" . $e->getFile() . "\n";
    echo "行号:" . $e->getLine() . "\n";
}

// 多个catch块
try {
    $data = json_decode('{"invalid": json}');
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new InvalidArgumentException("无效的JSON数据");
    }
    
    $file = fopen('nonexistent.txt', 'r');
    if (!$file) {
        throw new RuntimeException("无法打开文件");
    }
    
} catch (InvalidArgumentException $e) {
    echo "无效参数:" . $e->getMessage() . "\n";
} catch (RuntimeException $e) {
    echo "运行时错误:" . $e->getMessage() . "\n";
} catch (Exception $e) {
    echo "通用异常:" . $e->getMessage() . "\n";
}

// Finally块(PHP 5.5+)
try {
    $file = fopen('data.txt', 'r');
    // 处理文件
    throw new Exception("处理错误");
} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
} finally {
    // 这里总是执行
    if (isset($file) && $file) {
        fclose($file);
        echo "文件已关闭\n";
    }
}
?>

自定义异常类

php
<?php
// 基本自定义异常
class AppException extends Exception {
    protected $context = [];
    
    public function __construct($message = "", $code = 0, Exception $previous = null, array $context = []) {
        parent::__construct($message, $code, $previous);
        $this->context = $context;
    }
    
    public function getContext() {
        return $this->context;
    }
    
    public function getFullMessage() {
        $message = $this->getMessage();
        if (!empty($this->context)) {
            $message .= " 上下文:" . json_encode($this->context);
        }
        return $message;
    }
}

// 特定异常类型
class ValidationException extends AppException {
    private $errors = [];
    
    public function __construct($errors, $message = "验证失败") {
        $this->errors = $errors;
        parent::__construct($message, 400, null, ['errors' => $errors]);
    }
    
    public function getErrors() {
        return $this->errors;
    }
}

class DatabaseException extends AppException {
    public function __construct($message, $query = null, Exception $previous = null) {
        $context = [];
        if ($query) {
            $context['query'] = $query;
        }
        parent::__construct($message, 500, $previous, $context);
    }
}

class AuthenticationException extends AppException {
    public function __construct($message = "认证失败") {
        parent::__construct($message, 401);
    }
}

class AuthorizationException extends AppException {
    public function __construct($message = "访问被拒绝") {
        parent::__construct($message, 403);
    }
}

// Usage examples
function validateUser($data) {
    $errors = [];
    
    if (empty($data['name'])) {
        $errors['name'] = '姓名是必需的';
    }
    
    if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = '有效的邮箱是必需的';
    }
    
    if (!empty($errors)) {
        throw new ValidationException($errors);
    }
    
    return true;
}

function authenticateUser($username, $password) {
    // 模拟认证
    if ($username !== 'admin' || $password !== 'secret') {
        throw new AuthenticationException("无效的用户名或密码");
    }
    
    return ['id' => 1, 'username' => $username, 'role' => 'admin'];
}

function checkPermission($user, $resource) {
    if ($user['role'] !== 'admin') {
        throw new AuthorizationException("访问$resource需要管理员权限");
    }
}

// 异常处理实战
try {
    $userData = ['name' => '', 'email' => 'invalid-email'];
    validateUser($userData);
    
} catch (ValidationException $e) {
    echo "验证错误:\n";
    foreach ($e->getErrors() as $field => $error) {
        echo "- $field: $error\n";
    }
}

try {
    $user = authenticateUser('user', 'wrong');
    checkPermission($user, 'admin_panel');
    
} catch (AuthenticationException $e) {
    echo "认证错误:" . $e->getMessage() . "\n";
} catch (AuthorizationException $e) {
    echo "权限错误:" . $e->getMessage() . "\n";
}
?>

异常链和重新抛出

php
<?php
class DataProcessor {
    public function processFile($filename) {
        try {
            $data = $this->readFile($filename);
            return $this->parseData($data);
        } catch (Exception $e) {
            // 重新抛出并添加额外上下文
            throw new RuntimeException(
                "处理文件失败:$filename",
                0,
                $e // 之前的异常
            );
        }
    }
    
    private function readFile($filename) {
        try {
            if (!file_exists($filename)) {
                throw new InvalidArgumentException("文件不存在:$filename");
            }
            
            $content = file_get_contents($filename);
            if ($content === false) {
                throw new RuntimeException("无法读取文件:$filename");
            }
            
            return $content;
        } catch (Exception $e) {
            // 添加更多上下文并重新抛出
            throw new RuntimeException(
                "文件读取失败",
                0,
                $e
            );
        }
    }
    
    private function parseData($data) {
        $parsed = json_decode($data, true);
        
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new InvalidArgumentException(
                "无效的JSON数据:" . json_last_error_msg()
            );
        }
        
        return $parsed;
    }
}

// 使用异常追踪
$processor = new DataProcessor();

try {
    $result = $processor->processFile('nonexistent.json');
} catch (Exception $e) {
    echo "主要错误:" . $e->getMessage() . "\n";
    
    // 通过异常链进行追踪
    $current = $e;
    $level = 0;
    
    while ($current !== null) {
        echo str_repeat("  ", $level) . "级别$level:" . $current->getMessage() . "\n";
        echo str_repeat("  ", $level) . "文件:" . $current->getFile() . ":" . $current->getLine() . "\n";
        
        $current = $current->getPrevious();
        $level++;
    }
}
?>

自定义错误处理器

设置错误处理器

php
<?php
class ErrorHandler {
    private $logFile;
    private $displayErrors;
    
    public function __construct($logFile = 'error.log', $displayErrors = false) {
        $this->logFile = $logFile;
        $this->displayErrors = $displayErrors;
    }
    
    public function register() {
        set_error_handler([$this, 'handleError']);
        set_exception_handler([$this, 'handleException']);
        register_shutdown_function([$this, 'handleShutdown']);
    }
    
    public function handleError($severity, $message, $file, $line) {
        // 不处理用@抑制的错误
        if (!(error_reporting() & $severity)) {
            return false;
        }
        
        $errorInfo = [
            'type' => 'PHP错误',
            'severity' => $this->getSeverityName($severity),
            'message' => $message,
            'file' => $file,
            'line' => $line,
            'timestamp' => date('Y-m-d H:i:s'),
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'CLI'
        ];
        
        $this->logError($errorInfo);
        
        if ($this->displayErrors) {
            $this->displayError($errorInfo);
        }
        
        // 将错误转换为异常处理致命错误
        if ($severity === E_ERROR || $severity === E_CORE_ERROR || $severity === E_COMPILE_ERROR) {
            throw new ErrorException($message, 0, $severity, $file, $line);
        }
        
        return true; // 不执行PHP内部错误处理器
    }
    
    public function handleException($exception) {
        $errorInfo = [
            'type' => '未捕获异常',
            'class' => get_class($exception),
            'message' => $exception->getMessage(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString(),
            'timestamp' => date('Y-m-d H:i:s'),
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI'
        ];
        
        $this->logError($errorInfo);
        
        if ($this->displayErrors) {
            $this->displayException($errorInfo);
        } else {
            $this->displayGenericError();
        }
    }
    
    public function handleShutdown() {
        $error = error_get_last();
        
        if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
            $errorInfo = [
                'type' => '致命错误',
                'severity' => $this->getSeverityName($error['type']),
                'message' => $error['message'],
                'file' => $error['file'],
                'line' => $error['line'],
                'timestamp' => date('Y-m-d H:i:s')
            ];
            
            $this->logError($errorInfo);
            
            if ($this->displayErrors) {
                $this->displayError($errorInfo);
            } else {
                $this->displayGenericError();
            }
        }
    }
    
    private function logError($errorInfo) {
        $logEntry = "[{$errorInfo['timestamp']}] {$errorInfo['type']}: {$errorInfo['message']} " .
                   "in {$errorInfo['file']} on line {$errorInfo['line']}\n";
        
        if (isset($errorInfo['trace'])) {
            $logEntry .= "堆栈追踪:\n{$errorInfo['trace']}\n";
        }
        
        $logEntry .= str_repeat('-', 80) . "\n";
        
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
    
    private function displayError($errorInfo) {
        if (php_sapi_name() === 'cli') {
            echo "\n{$errorInfo['type']}: {$errorInfo['message']}\n";
            echo "文件:{$errorInfo['file']}:{$errorInfo['line']}\n\n";
        } else {
            echo "<div style='background: #ffebee; border: 1px solid #f44336; padding: 10px; margin: 10px;'>";
            echo "<strong>{$errorInfo['type']}:</strong> {$errorInfo['message']}<br>";
            echo "<strong>文件:</strong> {$errorInfo['file']}:{$errorInfo['line']}";
            echo "</div>";
        }
    }
    
    private function displayException($errorInfo) {
        if (php_sapi_name() === 'cli') {
            echo "\n未捕获{$errorInfo['class']}: {$errorInfo['message']}\n";
            echo "文件:{$errorInfo['file']}:{$errorInfo['line']}\n";
            echo "堆栈追踪:\n{$errorInfo['trace']}\n\n";
        } else {
            echo "<div style='background: #ffebee; border: 1px solid #f44336; padding: 15px; margin: 10px;'>";
            echo "<h3>未捕获{$errorInfo['class']}</h3>";
            echo "<p><strong>消息:</strong> {$errorInfo['message']}</p>";
            echo "<p><strong>文件:</strong> {$errorInfo['file']}:{$errorInfo['line']}</p>";
            echo "<details><summary>堆栈追踪</summary><pre>{$errorInfo['trace']}</pre></details>";
            echo "</div>";
        }
    }
    
    private function displayGenericError() {
        if (php_sapi_name() !== 'cli') {
            http_response_code(500);
            echo "<h1>内部服务器错误</h1>";
            echo "<p>处理您的请求时发生错误。请稍后再试。</p>";
        } else {
            echo "发生内部错误。\n";
        }
    }
    
    private function getSeverityName($severity) {
        $severities = [
            E_ERROR => 'E_ERROR',
            E_WARNING => 'E_WARNING',
            E_PARSE => 'E_PARSE',
            E_NOTICE => 'E_NOTICE',
            E_CORE_ERROR => 'E_CORE_ERROR',
            E_CORE_WARNING => 'E_CORE_WARNING',
            E_COMPILE_ERROR => 'E_COMPILE_ERROR',
            E_COMPILE_WARNING => 'E_COMPILE_WARNING',
            E_USER_ERROR => 'E_USER_ERROR',
            E_USER_WARNING => 'E_USER_WARNING',
            E_USER_NOTICE => 'E_USER_NOTICE',
            E_STRICT => 'E_STRICT',
            E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
            E_DEPRECATED => 'E_DEPRECATED',
            E_USER_DEPRECATED => 'E_USER_DEPRECATED'
        ];
        
        return $severities[$severity] ?? 'UNKNOWN';
    }
}

// 使用
$errorHandler = new ErrorHandler('app_errors.log', true); // 在开发中显示错误
$errorHandler->register();

// 测试错误处理器
echo $undefinedVariable; // 注意
trigger_error("自定义警告", E_USER_WARNING); // 警告
throw new Exception("测试异常"); // 异常
?>

日志系统

简单日志器实现

php
<?php
class Logger {
    const LEVEL_DEBUG = 1;
    const LEVEL_INFO = 2;
    const LEVEL_WARNING = 3;
    const LEVEL_ERROR = 4;
    const LEVEL_CRITICAL = 5;
    
    private $logFile;
    private $minLevel;
    private $maxFileSize;
    
    public function __construct($logFile = 'app.log', $minLevel = self::LEVEL_INFO, $maxFileSize = 10485760) {
        $this->logFile = $logFile;
        $this->minLevel = $minLevel;
        $this->maxFileSize = $maxFileSize; // 默认10MB
    }
    
    public function debug($message, array $context = []) {
        $this->log(self::LEVEL_DEBUG, $message, $context);
    }
    
    public function info($message, array $context = []) {
        $this->log(self::LEVEL_INFO, $message, $context);
    }
    
    public function warning($message, array $context = []) {
        $this->log(self::LEVEL_WARNING, $message, $context);
    }
    
    public function error($message, array $context = []) {
        $this->log(self::LEVEL_ERROR, $message, $context);
    }
    
    public function critical($message, array $context = []) {
        $this->log(self::LEVEL_CRITICAL, $message, $context);
    }
    
    public function log($level, $message, array $context = []) {
        if ($level < $this->minLevel) {
            return;
        }
        
        $this->rotateLogIfNeeded();
        
        $levelName = $this->getLevelName($level);
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = !empty($context) ? ' ' . json_encode($context) : '';
        
        $logEntry = "[$timestamp] [$levelName] $message$contextStr\n";
        
        file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
    
    public function logException(Exception $exception, $level = self::LEVEL_ERROR) {
        $context = [
            'exception' => get_class($exception),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString()
        ];
        
        $this->log($level, $exception->getMessage(), $context);
    }
    
    private function rotateLogIfNeeded() {
        if (!file_exists($this->logFile)) {
            return;
        }
        
        if (filesize($this->logFile) > $this->maxFileSize) {
            $backupFile = $this->logFile . '.' . date('Y-m-d-H-i-s');
            rename($this->logFile, $backupFile);
        }
    }
    
    private function getLevelName($level) {
        $levels = [
            self::LEVEL_DEBUG => 'DEBUG',
            self::LEVEL_INFO => 'INFO',
            self::LEVEL_WARNING => 'WARNING',
            self::LEVEL_ERROR => 'ERROR',
            self::LEVEL_CRITICAL => 'CRITICAL'
        ];
        
        return $levels[$level] ?? 'UNKNOWN';
    }
}

// Advanced logger with multiple handlers
class MultiLogger {
    private $handlers = [];
    
    public function addHandler(LoggerInterface $handler) {
        $this->handlers[] = $handler;
    }
    
    public function log($level, $message, array $context = []) {
        foreach ($this->handlers as $handler) {
            $handler->log($level, $message, $context);
        }
    }
    
    // 委托方法
    public function debug($message, array $context = []) {
        $this->log(Logger::LEVEL_DEBUG, $message, $context);
    }
    
    public function info($message, array $context = []) {
        $this->log(Logger::LEVEL_INFO, $message, $context);
    }
    
    public function warning($message, array $context = []) {
        $this->log(Logger::LEVEL_WARNING, $message, $context);
    }
    
    public function error($message, array $context = []) {
        $this->log(Logger::LEVEL_ERROR, $message, $context);
    }
    
    public function critical($message, array $context = []) {
        $this->log(Logger::LEVEL_CRITICAL, $message, $context);
    }
}

interface LoggerInterface {
    public function log($level, $message, array $context = []);
}

class FileLoggerHandler implements LoggerInterface {
    private $logger;
    
    public function __construct($logFile) {
        $this->logger = new Logger($logFile);
    }
    
    public function log($level, $message, array $context = []) {
        $this->logger->log($level, $message, $context);
    }
}

class EmailLoggerHandler implements LoggerInterface {
    private $email;
    private $minLevel;
    
    public function __construct($email, $minLevel = Logger::LEVEL_ERROR) {
        $this->email = $email;
        $this->minLevel = $minLevel;
    }
    
    public function log($level, $message, array $context = []) {
        if ($level >= $this->minLevel) {
            $subject = "Application Error - Level $level";
            $body = "Message: $message\n";
            $body .= "Context: " . json_encode($context, JSON_PRETTY_PRINT);
            $body .= "\nTime: " . date('Y-m-d H:i:s');
            
            mail($this->email, $subject, $body);
        }
    }
}

// 使用
$logger = new MultiLogger();
$logger->addHandler(new FileLoggerHandler('app.log'));
$logger->addHandler(new EmailLoggerHandler('admin@example.com'));

$logger->info('应用程序已启动');
$logger->warning('磁盘空间不足', ['available' => '500MB']);
$logger->error('数据库连接失败', ['host' => 'localhost', 'port' => 3306]);
?>

调试技术

调试信息和回溯

php
<?php
class Debugger {
    public static function dump($variable, $label = null) {
        if ($label) {
            echo "<h4>$label</h4>";
        }
        
        echo "<pre>";
        var_dump($variable);
        echo "</pre>";
    }
    
    public static function backtrace($limit = 10) {
        $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit);
        
        echo "<h4>堆栈追踪:</h4>";
        echo "<ol>";
        
        foreach ($trace as $i => $frame) {
            $file = $frame['file'] ?? '未知';
            $line = $frame['line'] ?? '未知';
            $function = $frame['function'] ?? '未知';
            $class = $frame['class'] ?? '';
            $type = $frame['type'] ?? '';
            
            echo "<li>";
            echo "<strong>$class$type$function()</strong><br>";
            echo "文件:$file:$line";
            echo "</li>";
        }
        
        echo "</ol>";
    }
    
    public static function printMemoryUsage() {
        $memory = memory_get_usage(true);
        $peak = memory_get_peak_usage(true);
        
        echo "当前内存使用:" . self::formatBytes($memory) . "\n";
        echo "峰值内存使用:" . self::formatBytes($peak) . "\n";
    }
    
    public static function printExecutionTime($startTime = null) {
        static $start;
        
        if ($startTime !== null) {
            $start = $startTime;
            return;
        }
        
        if ($start === null) {
            $start = microtime(true);
            return;
        }
        
        $end = microtime(true);
        $execution = $end - $start;
        
        echo "执行时间:" . number_format($execution, 4) . " 秒\n";
    }
    
    public static function profileFunction($callback, $iterations = 1) {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        
        for ($i = 0; $i < $iterations; $i++) {
            call_user_func($callback);
        }
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        
        $executionTime = $endTime - $startTime;
        $memoryUsed = $endMemory - $startMemory;
        
        echo "函数性能分析结果:\n";
        echo "迭代次数:$iterations\n";
        echo "总时间:" . number_format($executionTime, 4) . " 秒\n";
        echo "平均时间:" . number_format($executionTime / $iterations, 6) . " 秒\n";
        echo "内存使用:" . self::formatBytes($memoryUsed) . "\n";
    }
    
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        
        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }
        
        return round($bytes, 2) . ' ' . $units[$i];
    }
}

// 调试辅助函数
function dd($variable, $label = null) {
    Debugger::dump($variable, $label);
    die();
}

function bt($limit = 10) {
    Debugger::backtrace($limit);
}

// 使用示例
$data = ['name' => '张三', 'age' => 30, 'hobbies' => ['阅读', '编程']];
Debugger::dump($data, '用户数据');

function testFunction() {
    function nestedFunction() {
        Debugger::backtrace();
    }
    nestedFunction();
}

testFunction();

// 分析函数
Debugger::profileFunction(function() {
    $sum = 0;
    for ($i = 0; $i < 100000; $i++) {
        $sum += $i;
    }
    return $sum;
}, 10);

Debugger::printMemoryUsage();
?>

断言和测试助手

php
<?php
class Assert {
    public static function true($condition, $message = '断言失败') {
        if (!$condition) {
            throw new AssertionError($message);
        }
    }
    
    public static function false($condition, $message = '断言失败') {
        if ($condition) {
            throw new AssertionError($message);
        }
    }
    
    public static function equals($expected, $actual, $message = '值不相等') {
        if ($expected !== $actual) {
            $message .= "。期望值:" . var_export($expected, true) . 
                       ",实际值:" . var_export($actual, true);
            throw new AssertionError($message);
        }
    }
    
    public static function notNull($value, $message = '值为空') {
        if ($value === null) {
            throw new AssertionError($message);
        }
    }
    
    public static function instanceOf($object, $class, $message = '对象不是类的实例') {
        if (!($object instanceof $class)) {
            $actualClass = is_object($object) ? get_class($object) : gettype($object);
            $message .= "。期望:$class,实际:$actualClass";
            throw new AssertionError($message);
        }
    }
    
    public static function throws($callback, $expectedException = Exception::class, $message = '期望的异常未被抛出') {
        try {
            call_user_func($callback);
            throw new AssertionError($message);
        } catch (Exception $e) {
            if (!($e instanceof $expectedException)) {
                $actualClass = get_class($e);
                throw new AssertionError("期望$expectedException,得到$actualClass:" . $e->getMessage());
            }
        }
    }
}

class AssertionError extends Exception {}

// 测试辅助类
class TestRunner {
    private $tests = [];
    private $passed = 0;
    private $failed = 0;
    
    public function addTest($name, $callback) {
        $this->tests[$name] = $callback;
    }
    
    public function run() {
        echo "运行测试…\n\n";
        
        foreach ($this->tests as $name => $callback) {
            try {
                call_user_func($callback);
                echo "✓ $name\n";
                $this->passed++;
            } catch (Exception $e) {
                echo "✗ $name: " . $e->getMessage() . "\n";
                $this->failed++;
            }
        }
        
        echo "\n结果:{$this->passed}个通过,{$this->failed}个失败\n";
    }
}

// 示例使用
$runner = new TestRunner();

$runner->addTest('测试基本数学', function() {
    Assert::equals(4, 2 + 2, '加法应该有效');
    Assert::equals(0, 2 - 2, '减法应该有效');
});

$runner->addTest('测试字符串操作', function() {
    Assert::equals('你好世界', '你好' . '世界', '字符串连接');
    Assert::equals(2, mb_strlen('你好'), '字符串长度');
});

$runner->addTest('测试异常抛出', function() {
    Assert::throws(function() {
        throw new InvalidArgumentException('测试异常');
    }, InvalidArgumentException::class);
});

$runner->run();
?>

生产错误处理

错误监控和警报

php
<?php
class ErrorMonitor {
    private $config;
    private $logger;
    
    public function __construct($config) {
        $this->config = $config;
        $this->logger = new Logger($config['log_file']);
    }
    
    public function handleError($error) {
        // 记录错误
        $this->logger->error($error['message'], $error);
        
        // 检查是否应该警报
        if ($this->shouldAlert($error)) {
            $this->sendAlert($error);
        }
        
        // 更新错误统计
        $this->updateStats($error);
    }
    
    private function shouldAlert($error) {
        // 对关键错误进行警报
        if ($error['severity'] >= Logger::LEVEL_CRITICAL) {
            return true;
        }
        
        // 如果错误率过高则警报
        $recentErrors = $this->getRecentErrorCount();
        if ($recentErrors > $this->config['max_errors_per_minute']) {
            return true;
        }
        
        // 对特定错误模式进行警报
        foreach ($this->config['alert_patterns'] as $pattern) {
            if (strpos($error['message'], $pattern) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    private function sendAlert($error) {
        $alertData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'error' => $error,
            'server' => $_SERVER['SERVER_NAME'] ?? 'CLI',
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI'
        ];
        
        // 发送邮件警报
        if ($this->config['email_alerts']) {
            $this->sendEmailAlert($alertData);
        }
        
        // 发送到监控服务(例如Slack、PagerDuty)
        if ($this->config['webhook_url']) {
            $this->sendWebhookAlert($alertData);
        }
    }
    
    private function sendEmailAlert($alertData) {
        $subject = "关键错误警报 - " . $alertData['server'];
        $body = "发生了关键错误:\n\n";
        $body .= "时间:" . $alertData['timestamp'] . "\n";
        $body .= "消息:" . $alertData['error']['message'] . "\n";
        $body .= "文件:" . $alertData['error']['file'] . ":" . $alertData['error']['line'] . "\n";
        $body .= "URL:" . $alertData['url'] . "\n";
        
        mail($this->config['alert_email'], $subject, $body);
    }
    
    private function sendWebhookAlert($alertData) {
        $payload = json_encode([
            'text' => '关键错误警报',
            'attachments' => [
                [
                    'color' => 'danger',
                    'fields' => [
                        ['title' => '消息', 'value' => $alertData['error']['message'], 'short' => false],
                        ['title' => '文件', 'value' => $alertData['error']['file'] . ':' . $alertData['error']['line'], 'short' => true],
                        ['title' => '服务器', 'value' => $alertData['server'], 'short' => true]
                    ]
                ]
            ]
        ]);
        
        $ch = curl_init($this->config['webhook_url']);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        curl_close($ch);
    }
    
    private function getRecentErrorCount() {
        // 实现将检查上一分钟内的错误数
        // 这可以使用数据库、缓存或日志文件分析
        return 0; // 占位符
    }
    
    private function updateStats($error) {
        // 更新监控仪表盘的错误统计
        // 这可以写入数据库或指标服务
    }
}

// 配置
$errorConfig = [
    'log_file' => 'errors.log',
    'email_alerts' => true,
    'alert_email' => 'admin@example.com',
    'webhook_url' => 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK',
    'max_errors_per_minute' => 10,
    'alert_patterns' => ['数据库', '支付', '认证']
];

$monitor = new ErrorMonitor($errorConfig);

// 与错误处理器集成
set_exception_handler(function($exception) use ($monitor) {
    $error = [
        'severity' => Logger::LEVEL_CRITICAL,
        'message' => $exception->getMessage(),
        'file' => $exception->getFile(),
        'line' => $exception->getLine(),
        'trace' => $exception->getTraceAsString()
    ];
    
    $monitor->handleError($error);
    
    // 显示用户友好的错误页面
    if (!headers_sent()) {
        http_response_code(500);
        include 'error_pages/500.html';
    }
});
?>

下一步

现在您已经了解了错误处理,让我们在文件处理中探索文件处理。

实践练习

  1. 为Web应用程序创建一个全面的错误处理系统
  2. 构建一个具有多种输出格式(文件、数据库、邮件)的日志系统
  3. 为不同类型的应用程序错误实现自定义异常类
  4. 创建一个具有性能分析和内存监控的调试工具包
  5. 为生产应用程序设置错误监控和警报

正确的错误处理对于构建健壮、可维护的PHP应用程序至关重要!

本站内容仅供学习和研究使用。