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 一样舒服。📖 小节
- Notice < Warning < Fatal Error 三级错误
- 开发环境
display_errors On,生产环境display_errors Off+log_errors On try { } catch (ExceptionType $e) { } finally { }异常处理模式- 自定义异常类(
class XxxException extends Exception) error_log()记录日志,var_dump() + exit快速调试- 白屏排查:先开
display_errors,再看 error log - 输出缓冲
ob_start()/ob_get_clean()控制输出
📝 作业
- 写一个除法计算器页面,用 try/catch 捕获除数为 0 的情况并显示友好提示。
- 创建一个
AppException基类和ValidationException、DatabaseException子类,在用户注册逻辑中分别使用并 catch 处理。 - 给第2题加上日志功能:catch 异常时把错误信息记录到日志文件,包含时间、文件、行号。