PHP: 错误处理与调试

写了 32 课代码,肯定踩过不少坑——白屏、报错信息看不懂、try/catch 不知道怎么用。这节课把所有调试技能串起来,让你从"靠运气找 bug"升级为"系统性排错"。

1. 错误级别

PHP 有三种错误类型:

TEXT 📖 仅展示
Notice      ← "你可能有疏忽"(未定义变量、偏移量)
Warning     ← "代码有问题但能继续跑"(include找不到文件)
Fatal Error ← "代码有严重错误,必须停"(调用了不存在的函数)
PHP
<?php
// Notice:未定义变量(PHP 继续执行)
echo $undefinedVar;  // Notice: Undefined variable $undefinedVar

// Warning:文件不存在(PHP 继续执行)
include 'nonexistent.php';  // Warning: Failed opening...

// Fatal Error:调用不存在的函数(PHP 停止)
thisFunctionDoesNotExist();  // Fatal Error: Call to undefined function
?>

2. 控制错误报告

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

// 生产环境:记录但不显示
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/php-errors.log');
?>
🔥 易错: 生产服务器永远不要 display_errors = On——错误信息会暴露文件路径、数据库密码等敏感信息。用户看到 500 错误页就够了,错误详情记日志。

▶ 示例:环境自适应配置

PHP
<?php
$isDev = ($_SERVER['SERVER_NAME'] ?? '') === 'localhost';

if ($isDev) {
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
} else {
    error_reporting(E_ALL);
    ini_set('display_errors', '0');
    ini_set('log_errors', '1');
    ini_set('error_log', '/var/log/php/errors.log');
}
?>
▶ 试一试

3. 异常处理 try/catch/finally

PHP
<?php
function divide(float $a, float $b): float {
    if ($b === 0.0) {
        throw new Exception("除数不能为 0!");
    }
    return $a / $b;
}

try {
    echo divide(10, 2) . "<br>";  // 5
    echo divide(10, 0) . "<br>";  // 抛出异常
} catch (Exception $e) {
    echo "【捕获异常】{$e->getMessage()}<br>";
    echo "文件:{$e->getFile()}<br>";
    echo "行号:{$e->getLine()}<br>";
} finally {
    echo "无论成功失败,finally 都会执行(如关闭文件、释放资源)<br>";
}
?>
何时执行
try 从这里开始,检测异常
catch try 里有异常时执行
finally 无论如何都执行(常用于清理)

4. 自定义异常

PHP
<?php
class ValidationException extends Exception {
    private array $errors;
    
    public function __construct(array $errors, string $message = "验证失败") {
        parent::__construct($message);
        $this->errors = $errors;
    }
    
    public function getErrors(): array {
        return $this->errors;
    }
}

class UserNotFoundException extends Exception {}

// 使用自定义异常
function findUser(int $id): array {
    $users = [1 => ['name' => '小明'], 2 => ['name' => '小红']];
    
    if (!isset($users[$id])) {
        throw new UserNotFoundException("用户 ID {$id} 不存在");
    }
    return $users[$id];
}

function validateUsername(string $name): void {
    $errors = [];
    if ($name === '') $errors[] = '用户名不能为空';
    if (strlen($name) > 50) $errors[] = '用户名过长';
    if (!empty($errors)) throw new ValidationException($errors);
}

// 统一异常处理
try {
    validateUsername('');
    $user = findUser(99);
} catch (ValidationException $e) {
    echo "【验证失败】<br>" . implode("<br>", $e->getErrors());
} catch (UserNotFoundException $e) {
    echo "【用户未找到】{$e->getMessage()}";
} catch (Exception $e) {
    echo "【未知错误】{$e->getMessage()}";
}
?>

5. 日志记录

PHP
<?php
class AppLogger {
    public function __construct(
        private string $logDir
    ) {
        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }
    }
    
    public function info(string $message, array $context = []): void {
        $this->log('INFO', $message, $context);
    }
    
    public function error(string $message, array $context = []): void {
        $this->log('ERROR', $message, $context);
    }
    
    private function log(string $level, string $message, array $context): void {
        $date = date('Y-m-d');
        $time = date('H:i:s');
        
        $line = "[{$time}] [{$level}] {$message}";
        if (!empty($context)) {
            $line .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);
        }
        $line .= "\n";
        
        $file = "{$this->logDir}/app-{$date}.log";
        file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
    }
}

$log = new AppLogger(__DIR__ . '/logs');
$log->info('用户登录', ['username' => '小明', 'ip' => $_SERVER['REMOTE_ADDR'] ?? '']);
$log->error('支付失败', ['order_id' => 12345, 'reason' => '余额不足']);
?>

6. 调试技巧

▶ 示例:调试工具函数

PHP
<?php
// 最常用的调试输出
$data = ['name' => '小明', 'skills' => ['PHP', 'MySQL']];

var_dump($data);  // 详细输出(类型+值)
// array(2) { ["name"]=> string(6) "小明" ["skills"]=> ... }

print_r($data);   // 简洁输出
// Array ( [name] => 小明 [skills] => Array ( [0] => PHP [1] => MySQL ) )

// 格式化 var_dump(推荐)
echo '<pre>'; print_r($data); echo '</pre>';

// 调试并终止
var_dump($data); exit;  // 或 die(var_dump($data));
?>
▶ 试一试

(1) 常用调试快速检查表

PHP
<?php
// 值 + 类型
var_dump($variable);

// 只在开发环境显示
if ($isDev) {
    echo '<pre>'; var_dump($data); echo '</pre>';
}

// 记录到日志
error_log("调试信息:" . print_r($data, true));

// 检查是否到达某行代码
echo "HERE"; exit;  // 或 error_log("执行到了这里");

// 查看调用栈
debug_print_backtrace();

// 检查变量是否设置
var_dump(isset($var), empty($var));
?>

7. 输出缓冲和自定义错误处理器

PHP
<?php
// 输出缓冲:捕获 echo/print 等输出
ob_start();
echo "这些文字不会直接发送到浏览器。";
$content = ob_get_clean();  // 获取并清除缓冲区
// $content 现在是 "这些文字不会直接发送到浏览器。"

// 自定义错误处理器
function myErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool {
    $message = "[{$errno}] {$errstr} in {$errfile}:{$errline}";
    error_log($message);
    
    if (ini_get('display_errors')) {
        echo "<div style='background:#ffebee;padding:10px;margin:10px'>
            <strong>PHP Error:</strong> {$message}
        </div>";
    }
    return true;  // 阻止 PHP 默认处理
}
set_error_handler('myErrorHandler');

// 测试自定义错误处理器
echo $undefined;  // 现在会显示在漂亮的 HTML 框里
?>

❓ 常见问题

Q 白屏(Blank Page)怎么调试?
A 最常见原因是 Fatal Error 但 display_errors = Off。排查步骤:(1) 加 error_reporting(E_ALL); ini_set('display_errors', '1');;(2) 检查 PHP error log;(3) 逐段 echo "1"; exit; 定位到哪行停的。
Q Xdebug 是什么?需要装吗?
A Xdebug 是 PHP 的专业调试扩展——设断点、单步执行、看变量。初学用 var_dump 就够,当项目超过 1000 行时值得安装。VS Code + Xdebug 的组合和浏览器 DevTools 一样舒服。

📖 小节

📝 作业

  1. 写一个除法计算器页面,用 try/catch 捕获除数为 0 的情况并显示友好提示。
  2. 创建一个 AppException 基类和 ValidationExceptionDatabaseException 子类,在用户注册逻辑中分别使用并 catch 处理。
  3. 给第2题加上日志功能:catch 异常时把错误信息记录到日志文件,包含时间、文件、行号。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏