做Web开发,错误处理是一个很重要但经常被忽视的话题。
很多PHP开发者,尤其是初学者,不重视错误处理。代码写得很随意,不检查返回值,不捕获异常,不处理错误。结果就是:程序一出错,就直接白屏,或者显示一大段错误信息,把数据库密码、文件路径等敏感信息都暴露出来。用户体验很差,也有安全隐患。
一个健壮的程序,应该能优雅地处理各种错误和异常——出错了不崩溃,给用户友好的提示,同时记录详细的错误日志,方便开发者排查问题。
PHP提供了完善的错误处理机制,包括错误报告、自定义错误处理、异常处理(try-catch)等。今天分享PHP错误处理的最佳实践,帮你写出更健壮、更友好的代码。
PHP的错误类型
PHP的错误分为多种类型,常见的:
- E_ERROR:致命错误,程序无法继续执行(如调用不存在的函数、内存耗尽)
- E_WARNING:警告,非致命错误,程序继续执行(如include不存在的文件)
- E_NOTICE:通知,可能是bug也可能不是(如使用未定义的变量)
- E_PARSE:解析错误,语法错误,代码无法执行
- E_DEPRECATED:废弃警告,使用了即将废弃的功能
- E_STRICT:严格标准,代码不符合最佳实践
- ERECOVERABLEERROR:可捕获的致命错误
开发环境应该显示所有错误,方便调试;生产环境应该隐藏错误,只记录日志。
错误报告配置
php.ini配置:
; 开发环境:显示所有错误
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
error_log = /var/log/php_errors.log
; 生产环境:隐藏错误,只记录日志
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log代码中动态配置:
// 开发环境
ini_set('display_errors', '1');
error_reporting(E_ALL);
// 生产环境
ini_set('display_errors', '0');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);注意:生产环境一定要关闭display_errors,避免暴露敏感信息。
自定义错误处理
PHP允许用seterrorhandler()自定义错误处理函数,将错误转换为异常或记录日志:
function customErrorHandler($errno, $errstr, $errfile, $errline) {
// 如果错误被@抑制,不处理
if (!(error_reporting() & $errno)) {
return false;
}
$errorTypes = [
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_NOTICE => 'E_NOTICE',
E_PARSE => 'E_PARSE',
E_DEPRECATED => 'E_DEPRECATED',
];
$type = isset($errorTypes[$errno]) ? $errorTypes[$errno] : 'UNKNOWN';
$message = "[$type] $errstr in $errfile on line $errline";
// 记录日志
error_log($message);
// 开发环境抛出异常,方便调试
if (ini_get('display_errors')) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
return true; // 阻止PHP默认错误处理
}
set_error_handler('customErrorHandler');异常处理(try-catch)
PHP 5引入了异常处理机制,用try-catch捕获和处理异常:
try {
// 可能抛出异常的代码
$result = divide(10, 0);
echo $result;
} catch (InvalidArgumentException $e) {
// 捕获特定类型的异常
echo "参数错误:" . $e->getMessage();
} catch (Exception $e) {
// 捕获所有异常
echo "发生错误:" . $e->getMessage();
} finally {
// 无论是否发生异常,都会执行(可选)
echo "执行完毕";
}抛出异常:
function divide($a, $b) {
if ($b == 0) {
throw new InvalidArgumentException('除数不能为0');
}
return $a / $b;
}异常的常用方法:
$e->getMessage():异常消息$e->getCode():异常代码$e->getFile():抛出异常的文件$e->getLine():抛出异常的行号$e->getTrace():堆栈跟踪(数组)$e->getTraceAsString():堆栈跟踪(字符串)
异常分类
PHP的异常体系:
Throwable:所有错误和异常的接口(PHP 7+)
- Error:PHP内部错误(PHP 7+) - TypeError:类型错误 - ParseError:解析错误 - ArithmeticError:算术错误 - DivisionByZeroError:除零错误 - Exception:用户级异常 - RuntimeException:运行时异常 - LogicException:逻辑异常 - InvalidArgumentException:无效参数异常 - PDOException:数据库异常 - ...
自定义异常:
class MyAppException extends Exception {}
class DatabaseException extends MyAppException {}
class ValidationException extends MyAppException {}
// 使用
try {
// ...
} catch (ValidationException $e) {
// 验证错误,返回400
} catch (DatabaseException $e) {
// 数据库错误,记录日志,返回500
} catch (Exception $e) {
// 其他错误
}全局异常捕获
在应用入口处设置全局异常处理器,捕获未处理的异常:
// 全局异常处理器
function exceptionHandler($e) {
// 记录详细错误日志
error_log("Uncaught exception: " . $e->getMessage() . "\n" . $e->getTraceAsString());
// 开发环境显示详细错误
if (ini_get('display_errors')) {
echo "<h1>发生错误</h1>";
echo "<p><strong>消息:</strong>" . $e->getMessage() . "</p>";
echo "<p><strong>文件:</strong>" . $e->getFile() . "</p>";
echo "<p><strong>行号:</strong>" . $e->getLine() . "</p>";
echo "<pre>" . $e->getTraceAsString() . "</pre>";
} else {
// 生产环境显示友好错误页面
http_response_code(500);
include 'error_500.php';
}
}
set_exception_handler('exceptionHandler');错误日志
记录详细的错误日志,方便排查问题:
// 简单记录
error_log("Error: " . $message);
// 记录到指定文件
error_log("Error: " . $message, 3, '/var/log/myapp_errors.log');
// 发送邮件(严重错误)
error_log("Critical error: " . $message, 1, 'admin@example.com');日志应该包含:
- 错误时间
- 错误类型和消息
- 错误文件和行号
- 堆栈跟踪
- 请求URL和方法
- 用户IP和User-Agent
- 相关的请求参数(敏感信息脱敏)
友好的错误页面
生产环境出错时,不要显示技术细节,要显示友好的错误页面:
404页面(error_404.php):
<!DOCTYPE html>
<html>
<head><title>404 - 页面不存在</title></head>
<body>
<h1>404</h1>
<p>抱歉,您访问的页面不存在。</p>
<a href="/">返回首页</a>
</body>
</html>500页面(error_500.php):
<!DOCTYPE html>
<html>
<head><title>500 - 服务器错误</title></head>
<body>
<h1>500</h1>
<p>抱歉,服务器出了点问题,请稍后再试。</p>
<p>问题已记录,我们会尽快修复。</p>
<a href="/">返回首页</a>
</body>
</html>常见错误处理场景
1. 数据库操作:
try {
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
} catch (PDOException $e) {
error_log("Database error: " . $e->getMessage());
// 返回友好错误
http_response_code(500);
echo "数据库连接失败,请稍后再试";
}2. 文件操作:
$file = '/path/to/file.txt';
if (!file_exists($file)) {
throw new RuntimeException("文件不存在: $file");
}
if (!is_readable($file)) {
throw new RuntimeException("文件不可读: $file");
}
$content = file_get_contents($file);
if ($content === false) {
throw new RuntimeException("读取文件失败: $file");
}3. 外部API调用:
try {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new RuntimeException("API请求失败: $error");
}
if ($httpCode >= 400) {
throw new RuntimeException("API返回错误: HTTP $httpCode");
}
$data = json_decode($response, true);
if ($data === null) {
throw new RuntimeException("API返回数据解析失败");
}
} catch (Exception $e) {
error_log("API error: " . $e->getMessage());
// 使用缓存数据或返回默认值
}4. 用户输入验证:
function validateEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("邮箱格式不正确: $email");
}
return true;
}
try {
validateEmail($_POST['email']);
} catch (InvalidArgumentException $e) {
// 返回表单错误提示
echo $e->getMessage();
}错误处理最佳实践
- 开发环境显示所有错误,生产环境隐藏错误:方便调试,同时保证生产安全
- 永远不要忽略错误:不要用
@抑制错误,不要空的catch块 - 检查返回值:函数调用后检查返回值,特别是数据库、文件、网络操作
- 使用异常处理错误:用try-catch捕获异常,不要用返回错误码的方式(PHP风格)
- 记录详细日志:出错时记录详细信息,方便排查
- 给用户友好提示:不要把技术细节暴露给用户
- 全局异常捕获:在应用入口设置全局异常处理器,兜底处理未捕获的异常
- 合理的错误分类:区分用户错误(400)、未授权(401)、禁止访问(403)、未找到(404)、服务器错误(500)
- 不要在catch里什么都不做:至少记录日志,或者重新抛出异常
- finally块清理资源:关闭数据库连接、文件句柄等,用finally保证执行
总结
错误处理是写出健壮代码的关键。一个好的错误处理机制,应该:
- 开发环境显示详细错误,方便调试
- 生产环境隐藏错误,只记录日志
- 用try-catch捕获和处理异常
- 记录详细的错误日志
- 给用户友好的错误提示
- 全局兜底,避免白屏
不要忽视错误处理,它是代码质量的重要组成部分。从今天开始,给你的代码加上完善的错误处理吧。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录