在PHP开发中,错误处理是一个非常重要的话题。任何程序都可能出现错误,无论是语法错误、运行时错误、逻辑错误,还是外部资源错误(如数据库连接失败、文件不存在)。良好的错误处理,能让程序在出现错误时,给出友好的提示,记录详细的错误信息,方便调试和排查问题,而不是直接崩溃或显示空白页面。
PHP提供了丰富的错误处理机制,包括错误报告、错误日志、自定义错误处理、异常处理等。掌握这些机制,能帮你写出更健壮、更易维护的PHP代码。
今天分享PHP错误处理和异常的详解,从基础到进阶,帮你掌握PHP的错误处理机制。
PHP错误类型
PHP的错误分为多种类型,不同类型的错误,严重程度不同,处理方式也不同。
错误级别常量
- E_ERROR:致命错误,程序无法继续执行,如调用不存在的函数、内存耗尽
- E_WARNING:警告,程序可以继续执行,但可能有问题,如包含不存在的文件、除以0
- E_PARSE:解析错误,语法错误,程序无法执行
- E_NOTICE:通知,可能是错误,也可能是正常的,如使用未定义的变量
- ECOREERROR:PHP内核启动时的致命错误
- ECOREWARNING:PHP内核启动时的警告
- ECOMPILEERROR:编译时的致命错误
- ECOMPILEWARNING:编译时的警告
- EUSERERROR:用户触发的致命错误
- EUSERWARNING:用户触发的警告
- EUSERNOTICE:用户触发的通知
- E_STRICT:严格模式,建议改进代码以保证最佳互操作性和向前兼容性
- ERECOVERABLEERROR:可捕获的致命错误,如类型提示错误
- E_DEPRECATED:废弃警告,使用了即将被移除的功能
- EUSERDEPRECATED:用户触发的废弃警告
错误级别配置
可以在php.ini中配置错误报告级别:
; 显示所有错误
error_reporting = E_ALL
; 显示所有错误,除了通知
error_reporting = E_ALL & ~E_NOTICE
; 显示所有错误,除了通知和废弃警告
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED也可以在代码中动态配置:
// 显示所有错误
error_reporting(E_ALL);
// 显示所有错误,除了通知
error_reporting(E_ALL & ~E_NOTICE);
// 关闭所有错误显示
error_reporting(0);错误显示和日志
错误显示
; 是否显示错误
display_errors = On
; 是否显示启动错误
display_startup_errors = On开发环境建议开启错误显示,方便调试;生产环境建议关闭错误显示,避免泄露服务器信息,把错误记录到日志。
错误日志
; 是否记录错误日志
log_errors = On
; 错误日志文件路径
error_log = /var/log/php_errors.log
; 日志最大长度
log_errors_max_len = 1024也可以在代码中用error_log()函数记录错误:
error_log('这是一条错误日志');
error_log('这是一条错误日志', 3, '/path/to/error.log');基本错误处理
1. 错误抑制符@
在表达式前加@,可以抑制该表达式的错误:
$content = @file_get_contents('not_exist.txt');注意:@会抑制所有错误,包括致命错误,不建议滥用,只在确定可以忽略错误时使用。
2. 条件判断
对可能出错的操作,用条件判断:
$file = 'test.txt';
if (file_exists($file)) {
$content = file_get_contents($file);
} else {
echo '文件不存在';
}$conn = mysqli_connect('localhost', 'user', 'pass', 'db');
if (!$conn) {
die('数据库连接失败:' . mysqli_connect_error());
}3. trigger_error触发用户错误
if ($age < 0) {
trigger_error('年龄不能为负数', E_USER_WARNING);
}自定义错误处理函数
PHP允许用seterrorhandler()设置自定义错误处理函数,接管PHP的错误处理:
function myErrorHandler($errno, $errstr, $errfile, $errline) {
$errorTypes = [
E_ERROR => '致命错误',
E_WARNING => '警告',
E_PARSE => '解析错误',
E_NOTICE => '通知',
E_USER_ERROR => '用户致命错误',
E_USER_WARNING => '用户警告',
E_USER_NOTICE => '用户通知',
E_STRICT => '严格模式',
E_RECOVERABLE_ERROR => '可捕获错误',
E_DEPRECATED => '废弃警告',
E_USER_DEPRECATED => '用户废弃警告',
];
$type = isset($errorTypes[$errno]) ? $errorTypes[$errno] : '未知错误';
// 记录错误日志
error_log("[$type] $errstr in $errfile on line $errline");
// 开发环境显示错误
if (ini_get('display_errors')) {
echo "<b>$type</b>: $errstr in <b>$errfile</b> on line <b>$errline</b><br>";
}
// 返回true,表示错误已处理,不再执行PHP内部错误处理
return true;
}
// 设置自定义错误处理
set_error_handler('myErrorHandler');注意:自定义错误处理函数只能处理以下错误类型:EWARNING、ENOTICE、EUSER*、EDEPRECATED、ESTRICT、ERECOVERABLEERROR。EERROR、EPARSE等致命错误无法被自定义错误处理函数捕获,因为这些错误发生后程序会立即终止。
可以用restoreerrorhandler()恢复默认错误处理。
异常处理(Exception)
PHP 5引入了异常处理机制,和Java、C#等语言的异常处理类似。异常处理是面向对象的错误处理方式,更灵活、更强大。
1. 抛出异常
用throw关键字抛出异常:
function divide($a, $b) {
if ($b == 0) {
throw new Exception('除数不能为0');
}
return $a / $b;
}2. 捕获异常
用try-catch捕获异常:
try {
$result = divide(10, 0);
echo $result;
} catch (Exception $e) {
echo '捕获异常:' . $e->getMessage();
echo '异常文件:' . $e->getFile();
echo '异常行号:' . $e->getLine();
echo '异常追踪:' . $e->getTraceAsString();
}3. finally块
finally块中的代码,无论是否发生异常,都会执行:
try {
$result = divide(10, 0);
echo $result;
} catch (Exception $e) {
echo '捕获异常:' . $e->getMessage();
} finally {
echo 'finally块总是执行';
// 通常用来清理资源,如关闭数据库连接、文件句柄等
}4. 多个catch块
可以捕获不同类型的异常:
try {
// 代码
} catch (InvalidArgumentException $e) {
echo '参数错误:' . $e->getMessage();
} catch (RuntimeException $e) {
echo '运行时错误:' . $e->getMessage();
} catch (Exception $e) {
echo '其他错误:' . $e->getMessage();
}注意:catch块的顺序很重要,子类异常要放在父类异常前面,否则父类异常会先被子类捕获。
5. 自定义异常类
可以继承Exception类,创建自定义异常:
class DatabaseException extends Exception {
public function __construct($message, $code = 0, Exception $previous = null) {
parent::__construct($message, $code, $previous);
}
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}
class FileException extends Exception {}
// 使用自定义异常
try {
throw new DatabaseException('数据库连接失败');
} catch (DatabaseException $e) {
echo '数据库错误:' . $e->getMessage();
} catch (Exception $e) {
echo '其他错误:' . $e->getMessage();
}6. 异常方法
Exception类提供了以下方法:
getMessage():异常消息getCode():异常代码getFile():异常发生的文件getLine():异常发生的行号getTrace():异常追踪(数组)getTraceAsString():异常追踪(字符串)getPrevious():前一个异常(异常链)__toString():异常的字符串表示
错误和异常的区别
- 错误(Error):PHP的传统错误处理机制,主要用于PHP内部错误,如语法错误、运行时错误。错误有不同的级别(EERROR、EWARNING、ENOTICE等),可以用
seterror_handler()自定义处理。 - 异常(Exception):面向对象的错误处理机制,主要用于业务逻辑错误,如参数错误、业务规则违反。异常可以用
try-catch捕获,可以自定义异常类,可以有异常链。
在PHP 7中,引入了Error类,实现了Throwable接口,Error和Exception都实现了Throwable接口。所以在PHP 7中,可以用try-catch捕获Error:
try {
// 可能发生致命错误的代码
} catch (Error $e) {
echo '捕获错误:' . $e->getMessage();
} catch (Exception $e) {
echo '捕获异常:' . $e->getMessage();
}也可以用Throwable捕获所有错误和异常:
try {
// 代码
} catch (Throwable $e) {
echo '捕获到:' . $e->getMessage();
}实战:数据库操作的错误处理
下面是一个数据库操作的错误处理示例:
class Database {
private $pdo;
public function __construct($host, $dbname, $user, $pass) {
try {
$this->pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$user,
$pass,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
// 记录错误日志
error_log('数据库连接失败:' . $e->getMessage());
// 抛出自定义异常
throw new DatabaseException('数据库连接失败,请稍后重试');
}
}
public function query($sql, $params = []) {
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (PDOException $e) {
error_log('数据库查询失败:' . $e->getMessage() . ' SQL: ' . $sql);
throw new DatabaseException('数据库查询失败');
}
}
public function fetchAll($sql, $params = []) {
return $this->query($sql, $params)->fetchAll();
}
public function fetchOne($sql, $params = []) {
return $this->query($sql, $params)->fetch();
}
}
// 使用
try {
$db = new Database('localhost', 'test', 'user', 'pass');
$users = $db->fetchAll('SELECT * FROM users');
print_r($users);
} catch (DatabaseException $e) {
echo '错误:' . $e->getMessage();
} catch (Exception $e) {
echo '未知错误:' . $e->getMessage();
}这个示例中,数据库操作的错误都被捕获,记录详细日志,然后抛出友好的自定义异常,调用方捕获异常并给出友好提示。
错误处理最佳实践
- 开发环境显示所有错误:开发环境开启
displayerrors = On和errorreporting = E_ALL,方便调试。 - 生产环境关闭错误显示,记录日志:生产环境关闭
displayerrors,开启logerrors,把错误记录到日志,避免泄露服务器信息。 - 使用异常处理业务逻辑错误:业务逻辑错误(如参数错误、权限不足、业务规则违反)用异常处理,不要用普通的错误。
- 捕获异常后要处理:捕获异常后,要么处理(记录日志、给出提示、重试),要么重新抛出,不要空捕获(catch了什么都不做)。
- 不要捕获所有异常:只捕获你能处理的异常,不要用
catch (Exception $e)捕获所有异常然后忽略。 - 使用finally清理资源:需要清理的资源(数据库连接、文件句柄、锁等)放在finally块中,确保无论是否发生异常都能清理。
- 自定义异常类:为不同类型的错误创建自定义异常类,方便区分和处理。
- 异常消息要友好:给用户看的异常消息要友好,不要泄露技术细节;技术细节记录到日志。
- 不要用@抑制错误:除非确定可以忽略,否则不要用@抑制错误,这会隐藏问题。
- 记录详细的错误日志:错误日志要包含错误类型、消息、文件、行号、堆栈追踪、时间、用户IP等,方便排查问题。
- 设置全局异常处理:用
setexceptionhandler()设置全局异常处理,捕获未被catch的异常,给出友好的错误页面。 - 错误页面要友好:生产环境出错时,显示友好的错误页面,不要显示PHP的错误信息。
全局异常处理
用setexceptionhandler()设置全局异常处理:
function myExceptionHandler($e) {
// 记录错误日志
error_log('未捕获异常:' . $e->getMessage() . "\n" . $e->getTraceAsString());
// 开发环境显示详细错误
if (ini_get('display_errors')) {
echo '<h1>发生异常</h1>';
echo '<p>' . get_class($e) . ': ' . $e->getMessage() . '</p>';
echo '<p>文件:' . $e->getFile() . ' 行号:' . $e->getLine() . '</p>';
echo '<pre>' . $e->getTraceAsString() . '</pre>';
} else {
// 生产环境显示友好错误页面
http_response_code(500);
echo '<h1>服务器内部错误</h1>';
echo '<p>抱歉,服务器发生了内部错误,请稍后重试。</p>';
}
}
set_exception_handler('myExceptionHandler');常见问题
1. 为什么显示空白页面
原因:发生了致命错误,但displayerrors关闭了,所以显示空白。 解决:开发环境开启displayerrors = On,或者查看错误日志。
2. 为什么自定义错误处理函数不生效
原因:自定义错误处理函数只能处理部分错误类型,EERROR、EPARSE等致命错误无法被捕获。 解决:致命错误无法用自定义错误处理函数捕获,需要在php.ini中配置错误显示和日志。PHP 7中可以用try-catch捕获Error。
3. 异常和错误用哪个
建议:
- PHP内部错误(语法错误、运行时错误)用错误处理
- 业务逻辑错误(参数错误、权限不足、业务规则违反)用异常处理
- 新代码推荐用异常处理,更灵活、更面向对象
4. 如何调试错误
- 开发环境开启错误显示,看到详细错误信息
- 查看错误日志,找到错误发生的文件和行号
- 用
vardump()、printr()输出变量,调试逻辑 - 用Xdebug等调试工具,断点调试
- 用
debug_backtrace()查看函数调用栈
总结
PHP错误处理,是写出健壮代码的基础。
核心要点:
- 错误类型:EERROR、EWARNING、ENOTICE、EPARSE等,不同级别严重程度不同
- 错误配置:errorreporting、displayerrors、logerrors、errorlog
- 基本错误处理:@错误抑制符、条件判断、trigger_error
- 自定义错误处理:seterrorhandler,接管PHP错误处理
- 异常处理:throw抛出、try-catch捕获、finally清理、自定义异常类
- 错误和异常的区别:错误用于PHP内部错误,异常用于业务逻辑错误;PHP 7中Error也可以被catch
- 实战:数据库操作的错误处理,捕获异常、记录日志、抛出友好异常
- 最佳实践:开发显示错误、生产记录日志、用异常处理业务错误、捕获后要处理、finally清理资源、自定义异常、友好错误消息、详细日志、全局异常处理、友好错误页面
- 全局异常处理:setexceptionhandler,捕获未被catch的异常
良好的错误处理,能让你的程序在出现错误时,不崩溃、不泄露信息、给出友好提示、记录详细日志,方便排查问题。作为PHP开发者,一定要重视错误处理,写出健壮、易维护的代码。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录