本文目录导读:

在PHP项目中处理异常和错误是一个非常重要的环节,良好的处理机制能提升代码的健壮性和可维护性,下面我会从错误和异常两个方面来详细说明,并给出最佳实践。
核心概念区分
| 特性 | 错误 (Error) | 异常 (Exception) |
|---|---|---|
| 触发方式 | PHP 引擎自身触发 (如:致命错误、警告) | 手动 throw 或某些内置函数抛出 |
| 处理方式 | 错误处理器 (set_error_handler) |
try-catch |
是否能被 catch |
不能直接 catch,但可以通过错误处理器捕获 | 可以 catch |
| 典型场景 | 文件未找到、语法错误、内存耗尽 | 数据库连接失败、业务逻辑错误 |
PHP 7 以后引入了
Throwable接口,错误和异常都实现了该接口,因此可以用catch (Throwable $e)统一捕获。
处理错误的两种方式
使用 set_error_handler 自定义错误处理器
适用于处理 PHP 非致命错误(如 warning、notice):
// 自定义错误处理器
set_error_handler(function(int $errno, string $errstr, string $errfile, int $errline) {
// 记录日志
error_log("Error [$errno] $errstr in $errfile on line $errline");
// 可以根据错误级别决定是否抛异常
if ($errno === E_WARNING) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
// 返回 true 表示已处理,PHP 不再执行标准错误处理
return true;
});
// 触发一个 warning
$arr = [];
echo $arr['not_exists']; // 这个 warning 会被自定义处理器捕获
注意:此方式无法捕获致命错误(如
E_ERROR、核心错误)。
捕获致命错误:register_shutdown_function
对于致命错误(如内存溢出、语法错误),可以注册关闭函数:
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// 记录致命错误并友好提示
error_log("Fatal Error: " . json_encode($error));
http_response_code(500);
echo "系统繁忙,请稍后再试。";
}
});
处理异常的三种方式
try-catch-finally 基本结构
try {
$db = new PDO('mysql:host=invalid', 'user', 'pass');
} catch (PDOException $e) {
// 处理特定异常
error_log("数据库连接失败: " . $e->getMessage());
die("数据库连接失败,请联系管理员。");
} catch (Exception $e) {
// 处理其他异常
error_log("异常: " . $e->getMessage());
} finally {
// 无论是否捕获异常都会执行(可选)
echo "执行结束。";
}
设置默认异常处理器 set_exception_handler
当 try-catch 未捕获到的异常,PHP 会交给默认异常处理器,可以自定义:
set_exception_handler(function(Throwable $e) {
// 记录日志
error_log("未捕获异常: " . $e->getMessage());
// 返回用户友好的 500 页面
http_response_code(500);
if (defined('ENVIRONMENT') && ENVIRONMENT === 'development') {
echo "异常信息: " . $e->getMessage() . "<br>";
echo "文件: " . $e->getFile() . " 行: " . $e->getLine();
} else {
echo "服务器内部错误,请稍后再试。";
}
});
// 测试:抛出一个未捕获的异常
throw new RuntimeException("测试异常");
自定义异常类
扩展业务逻辑的可读性:
class InsufficientBalanceException extends Exception {}
class BankAccount {
public function withdraw($amount) {
if ($amount > $this->balance) {
throw new InsufficientBalanceException("余额不足,当前余额: {$this->balance}");
}
// 正常逻辑
}
}
try {
$account->withdraw(1000);
} catch (InsufficientBalanceException $e) {
echo $e->getMessage(); // 余额不足...
}
最佳实践总结
分层处理(推荐结构)
- 开发环境:显示详细错误信息和堆栈。
- 生产环境:记录日志,返回用户友好的错误页面(500 错误页)。
// config.php
define('DEBUG_MODE', true); // 上线改为 false
// 错误与异常处理器
if (DEBUG_MODE) {
// 开发环境:显示所有错误
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', '/path/to/php-error.log');
}
// 统一异常处理器
set_exception_handler(function(Throwable $e) {
error_log("[" . get_class($e) . "] " . $e->getMessage());
http_response_code(500);
if (DEBUG_MODE) {
echo "异常: " . $e->getMessage() . "<br>";
echo "文件: " . $e->getFile() . ":" . $e->getLine();
} else {
// 加载自定义错误页面
include 'errors/500.html';
}
});
永远不要向用户暴露真实错误信息
// ❌ 错误做法 echo "数据库错误: " . $e->getMessage(); // ✅ 正确做法 error_log($e->getMessage()); // 记录到日志 echo "系统处理请求时发生错误,请稍后再试。";
使用日志系统
推荐使用 monolog 等专业日志库(通常框架自带):
// 例如使用 monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('/path/to/app.log', Logger::WARNING));
try {
// 业务代码
} catch (Exception $e) {
$log->error($e->getMessage(), ['trace' => $e->getTraceAsString()]);
}
框架中的自动化处理
如果是现代框架(如 Laravel、Symfony、ThinkPHP),它们已经内置了完善的错误与异常处理机制:
- Laravel:
app/Exceptions/Handler.php中的render()和report()方法。 - ThinkPHP:配置
app.exception_handle全局接管。
你只需要在框架提供的异常处理类中编写你的业务逻辑即可。
完整示例:一个通用的错误异常处理类
class ErrorHandler {
public static function register() {
// 设置错误处理
set_error_handler([self::class, 'handleError']);
// 设置异常处理(针对未捕获的异常)
set_exception_handler([self::class, 'handleException']);
// 捕获致命错误
register_shutdown_function([self::class, 'handleFatalError']);
}
public static function handleError($level, $message, $file, $line) {
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
}
public static function handleException(Throwable $e) {
error_log("Uncaught Exception: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
http_response_code(500);
if (defined('DEBUG_MODE') && DEBUG_MODE) {
echo "<pre>" . $e . "</pre>";
} else {
echo "系统错误,请稍后重试。";
}
}
public static function handleFatalError() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
self::handleException(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']));
}
}
}
// 启动错误处理
ErrorHandler::register();
| 场景 | 处理方法 |
|---|---|
| 线上隐藏错误详情 | 关闭 display_errors,开启 log_errors |
| 所有错误都转异常 | set_error_handler 抛出 ErrorException |
| 未捕获的异常 | set_exception_handler 统一处理 |
| 致命错误 | register_shutdown_function |
| 业务逻辑异常 | 自定义异常类 + try-catch |
| 框架环境 | 利用框架提供的 Handler(如 Laravel 的 App\Exceptions\Handler) |
推荐做法:线上环境统一捕获所有 Throwable,记录日志,返回友好界面,开发环境则显示完整错误信息以便调试。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播