本文目录导读:

在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,记录日志,返回友好界面,开发环境则显示完整错误信息以便调试。