错误类型
什么是错误
在PHP程序运行过程中,可能会出现各种各样的问题,这些问题我们统称为"错误"。理解不同类型的错误对于编写健壮的PHP应用程序至关重要。
错误是指程序在执行过程中遇到的问题,这些问题可能导致程序无法正常执行或产生意外的结果。
PHP错误级别
PHP定义了多种错误级别,每种级别代表了不同严重程度的问题:
1. 致命错误(Fatal Errors)
致命错误会导致程序立即终止执行,无法继续运行。
<?php
// 致命错误示例1:调用不存在的函数
echo "程序开始执行\n";
$result = undefined_function(); // 致命错误:函数不存在
echo "这行不会执行\n"; // 这行永远不会执行
?>
<?php
// 致命错误示例2:访问不存在的类
$obj = new NonExistentClass(); // 致命错误:类不存在
?>
<?php
// 致命错误示例3:包含不存在的文件(require)
require 'non_existent_file.php'; // 致命错误,程序终止
echo "程序继续"; // 不会执行
?>
2. 警告错误(Warnings)
警告错误不会终止程序执行,但提示程序可能存在问题。
<?php
// 警告示例1:包含不存在的文件(include)
echo "程序开始执行\n";
include 'non_existent_file.php'; // 警告:文件不存在,但程序继续
echo "程序继续执行\n"; // 这行会执行
?>
<?php
// 警告示例2:错误的参数类型
echo strlen(123); // 警告:期望字符串,但传入了整数
echo "程序继续\n"; // 程序继续执行
?>
<?php
// 警告示例3:使用未定义的变量
echo $undefined_variable; // 警告:变量未定义
echo "程序继续\n";
?>
3. 通知错误(Notices)
通知错误表示程序存在轻微问题,通常不会影响程序执行。
<?php
// 通知示例1:使用未定义的数组索引
$array = ['a' => 1];
echo $array['b']; // 通知:索引 'b' 不存在
echo "程序继续\n";
?>
<?php
// 通知示例2:除以零
echo 10 / 0; // 通知:除以零,返回INF或false
echo "程序继续\n";
?>
4. 弃用错误(Deprecated)
弃用错误表示使用了在未来版本中可能会被移除的功能。
<?php
// 弃用示例:使用已弃用的函数
$result = ereg("pattern", "string"); // 弃用:ereg函数在PHP 7.0+中被移除
// 弃用示例:使用已弃用的语法
$object = &new stdClass(); // 弃用:引用语法已弃用
?>
错误报告级别
PHP提供了常量来表示不同的错误级别:
<?php
// 常用错误级别常量
echo E_ERROR; // 1 - 致命错误
echo E_WARNING; // 2 - 警告
echo E_PARSE; // 4 - 编译时解析错误
echo E_NOTICE; // 8 - 通知
echo E_CORE_ERROR; // 16 - PHP启动时的致命错误
echo E_CORE_WARNING; // 32 - PHP启动时的警告
echo E_COMPILE_ERROR; // 64 - 编译时的致命错误
echo E_COMPILE_WARNING; // 128- 编译时的警告
echo E_USER_ERROR; // 256- 用户定义的致命错误
echo E_USER_WARNING; // 512- 用户定义的警告
echo E_USER_NOTICE; // 1024- 用户定义的通知
echo E_STRICT; // 2048- 严格标准化建议
echo E_RECOVERABLE_ERROR; // 4096- 可捕获的致命错误
echo E_DEPRECATED; // 8192- 弃用警告
echo E_USER_DEPRECATED; // 16384- 用户定义的弃用警告
echo E_ALL; // 32767- 所有错误
?>
配置错误报告
在php.ini中配置
; 错误报告设置
error_reporting = E_ALL & ~E_DEPRECATED
display_errors = On ; 开发环境显示错误
display_errors = Off ; 生产环境不显示错误
log_errors = On ; 记录错误到日志
error_log = /var/log/php_errors.log ; 错误日志文件路径
; 常见配置组合
; 开发环境
error_reporting = E_ALL
display_errors = On
log_errors = On
; 生产环境
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
log_errors = On
在代码中配置
<?php
// 设置错误报告级别
error_reporting(E_ALL); // 报告所有错误
error_reporting(E_ALL & ~E_NOTICE); // 报告除通知外的所有错误
error_reporting(0); // 关闭所有错误报告
// 显示错误
ini_set('display_errors', 1); // 开启错误显示
ini_set('display_errors', 0); // 关闭错误显示
// 记录错误
ini_set('log_errors', 1); // 开启错误日志
ini_set('error_log', 'my_errors.log'); // 设置错误日志文件
// 动态配置示例
function setEnvironmentErrorReporting($environment) {
switch ($environment) {
case 'development':
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('log_errors', 1);
break;
case 'testing':
error_reporting(E_ALL & ~E_DEPRECATED);
ini_set('display_errors', 1);
ini_set('log_errors', 1);
break;
case 'production':
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
break;
}
}
// 使用示例
setEnvironmentErrorReporting('development');
?>
常见错误类型详解
1. 语法错误(Parse Errors)
语法错误是最基础的错误类型,通常在代码执行前就会被检测到。
<?php
// 缺少分号
echo "Hello World" // Parse error: syntax error, unexpected 'echo'
// 缺少引号
echo "Hello World; // Parse error: syntax error, unexpected end of file
// 括号不匹配
if ($condition { // Parse error: syntax error, unexpected '{'
echo "condition true";
}
// 错误的类定义
class MyClass { // Parse error: syntax error, unexpected end of file
public $property
public function method() {
echo "method";
}
}
?>
2. 逻辑错误(Logical Errors)
逻辑错误不会产生错误信息,但程序的行为不符合预期。
<?php
// 逻辑错误示例1:条件判断错误
function isAdult($age) {
return $age > 18; // 错误:应该是 >= 18
}
// 逻辑错误示例2:循环条件错误
function printNumbers($n) {
for ($i = 0; $i <= $n; $i++) { // 错误:应该是 < $n
echo $i . " ";
}
}
// 逻辑错误示例3:数组索引错误
function getFirstElement($array) {
return $array[1]; // 错误:应该是 $array[0]
}
// 逻辑错误示例4:字符串连接错误
function createFullName($firstName, $lastName) {
return $firstName + $lastName; // 错误:应该使用 . 连接
}
// 调试逻辑错误的方法
function safeDivide($a, $b) {
if ($b == 0) {
echo "错误:除数不能为零\n";
return false;
}
return $a / $b;
}
// 使用断言来检测逻辑错误
function calculateDiscount($amount, $discount) {
assert($amount >= 0, "金额必须大于等于0");
assert($discount >= 0 && $discount <= 100, "折扣必须在0-100之间");
return $amount * ($discount / 100);
}
?>
3. 运行时错误(Runtime Errors)
运行时错误在程序执行过程中发生。
<?php
// 运行时错误示例1:除以零
function divide($a, $b) {
if ($b == 0) {
trigger_error("除数不能为零", E_USER_WARNING);
return false;
}
return $a / $b;
}
// 运行时错误示例2:内存不足
function processLargeData() {
// 尝试分配过多内存
$largeArray = array_fill(0, 10000000, "large string");
}
// 运行时错误示例3:文件操作错误
function readFile($filename) {
$handle = fopen($filename, 'r');
if (!$handle) {
trigger_error("无法打开文件: $filename", E_USER_WARNING);
return false;
}
$content = fread($handle, filesize($filename));
fclose($handle);
return $content;
}
// 运行时错误示例4:数据库连接错误
function connectToDatabase() {
$conn = mysqli_connect("invalid_host", "user", "password", "database");
if (!$conn) {
trigger_error("数据库连接失败: " . mysqli_connect_error(), E_USER_ERROR);
return false;
}
return $conn;
}
?>
错误处理函数
trigger_error() - 触发用户定义的错误
<?php
function validateEmail($email) {
if (empty($email)) {
trigger_error("邮箱地址不能为空", E_USER_WARNING);
return false;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
trigger_error("邮箱地址格式不正确: $email", E_USER_WARNING);
return false;
}
return true;
}
function processUserRegistration($userData) {
// 验证必填字段
if (!isset($userData['name'])) {
trigger_error("用户名是必填字段", E_USER_ERROR);
}
if (!isset($userData['email'])) {
trigger_error("邮箱是必填字段", E_USER_ERROR);
}
// 验证邮箱格式
if (!validateEmail($userData['email'])) {
trigger_error("注册失败:邮箱验证错误", E_USER_NOTICE);
return false;
}
echo "用户注册成功\n";
return true;
}
// 使用示例
$userData = [
'name' => '',
'email' => 'invalid-email'
];
processUserRegistration($userData);
?>
set_error_handler() - 自定义错误处理
<?php
// 自定义错误处理函数
function customErrorHandler($errno, $errstr, $errfile, $errline) {
$errorType = [
E_ERROR => 'Error',
E_WARNING => 'Warning',
E_PARSE => 'Parse Error',
E_NOTICE => 'Notice',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice'
];
$type = isset($errorType[$errno]) ? $errorType[$errno] : 'Unknown';
$message = sprintf(
"[%s] %s: %s in %s on line %d\n",
date('Y-m-d H:i:s'),
$type,
$errstr,
$errfile,
$errline
);
// 记录到日志文件
file_put_contents('error.log', $message, FILE_APPEND);
// 根据错误类型决定是否显示错误
if (in_array($errno, [E_ERROR, E_USER_ERROR])) {
echo "系统发生错误,请联系管理员";
} else {
echo $message; // 开发环境显示详细错误
}
// 返回true表示已处理错误,不再使用PHP默认处理
return true;
}
// 设置自定义错误处理函数
set_error_handler('customErrorHandler');
// 测试错误处理
echo $undefinedVariable; // 触发通知错误
echo 10 / 0; // 触发警告错误
trigger_error("测试用户错误", E_USER_ERROR);
?>
restore_error_handler() - 恢复默认错误处理
<?php
function temporaryErrorHandler($errno, $errstr, $errfile, $errline) {
echo "临时错误处理器: $errstr\n";
return true;
}
// 设置临时错误处理器
set_error_handler('temporaryErrorHandler');
echo $undefinedVar; // 使用临时错误处理器
// 恢复默认错误处理器
restore_error_handler();
echo $anotherUndefinedVar; // 使用默认错误处理器
?>
错误日志记录
记录到文件
<?php
function logError($message, $level = 'ERROR') {
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[$timestamp] [$level] $message\n";
// 确保日志目录存在
$logDir = 'logs';
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
// 写入日志文件
$logFile = $logDir . '/app_' . date('Y-m-d') . '.log';
file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX);
}
function logErrorWithContext($errno, $errstr, $errfile, $errline) {
$context = [
'timestamp' => date('Y-m-d H:i:s'),
'level' => $errno,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'url' => $_SERVER['REQUEST_URI'] ?? 'CLI',
'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
'ip' => $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'
];
$logMessage = json_encode($context, JSON_UNESCAPED_UNICODE) . "\n";
file_put_contents('logs/context_errors.log', $logMessage, FILE_APPEND);
}
// 使用示例
logError("用户登录失败", "WARNING");
logError("数据库连接超时", "ERROR");
// 设置错误处理器记录详细错误
set_error_handler('logErrorWithContext');
?>
错误日志轮转
<?php
class ErrorLogger {
private $logDir;
private $maxFileSize;
private $maxFiles;
public function __construct($logDir = 'logs', $maxFileSize = 10485760, $maxFiles = 10) {
$this->logDir = $logDir;
$this->maxFileSize = $maxFileSize; // 10MB
$this->maxFiles = $maxFiles;
if (!is_dir($this->logDir)) {
mkdir($this->logDir, 0755, true);
}
}
public function log($message, $level = 'INFO') {
$timestamp = date('Y-m-d H:i:s');
$logEntry = "[$timestamp] [$level] $message\n";
$currentLogFile = $this->getCurrentLogFile();
// 检查文件大小,如果超过限制则轮转
if (file_exists($currentLogFile) && filesize($currentLogFile) > $this->maxFileSize) {
$this->rotateLog();
}
file_put_contents($currentLogFile, $logEntry, FILE_APPEND | LOCK_EX);
}
private function getCurrentLogFile() {
return $this->logDir . '/app.log';
}
private function rotateLog() {
$currentLogFile = $this->getCurrentLogFile();
// 移动现有的日志文件
for ($i = $this->maxFiles - 1; $i > 0; $i--) {
$oldFile = $currentLogFile . '.' . $i;
$newFile = $currentLogFile . '.' . ($i + 1);
if (file_exists($oldFile)) {
if ($i == $this->maxFiles - 1) {
unlink($oldFile); // 删除最旧的文件
} else {
rename($oldFile, $newFile);
}
}
}
// 重命名当前日志文件
if (file_exists($currentLogFile)) {
rename($currentLogFile, $currentLogFile . '.1');
}
}
public function cleanOldLogs($days = 30) {
$files = glob($this->logDir . '/*.log*');
$cutoffTime = time() - ($days * 24 * 60 * 60);
foreach ($files as $file) {
if (filemtime($file) < $cutoffTime) {
unlink($file);
}
}
}
}
// 使用示例
$logger = new ErrorLogger('logs', 1048576, 5); // 1MB,最多5个文件
$logger->log("应用程序启动", "INFO");
$logger->log("用户登录", "INFO");
$logger->log("处理请求", "DEBUG");
?>
开发环境 vs 生产环境
开发环境配置
<?php
function setDevelopmentEnvironment() {
// 显示所有错误
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
// 设置详细的错误报告
ini_set('log_errors', 1);
ini_set('error_log', 'dev_errors.log');
// 启用调试信息
ini_set('html_errors', 1);
ini_set('docref_root', 'http://php.net/manual/en/');
echo "开发环境错误处理已启用\n";
}
// 开发环境的错误处理器
function developmentErrorHandler($errno, $errstr, $errfile, $errline) {
echo "<div style='background: #ffeeee; border: 1px solid #ff0000; padding: 10px; margin: 10px;'>";
echo "<strong>错误:</strong> $errstr<br>";
echo "<strong>文件:</strong> $errfile<br>";
echo "<strong>行号:</strong> $errline<br>";
// 显示调用栈
echo "<strong>调用栈:</strong><br>";
debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
echo "</div>";
return true;
}
setDevelopmentEnvironment();
set_error_handler('developmentErrorHandler');
?>
生产环境配置
<?php
function setProductionEnvironment() {
// 不显示错误给用户
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
// 记录所有错误
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php/production_errors.log');
// 设置错误日志格式
ini_set('log_errors_max_len', 1024);
echo "生产环境错误处理已配置\n";
}
// 生产环境的错误处理器
function productionErrorHandler($errno, $errstr, $errfile, $errline) {
// 记录错误到文件
$logEntry = sprintf(
"[%s] [%d] %s in %s on line %d [URL: %s] [IP: %s]",
date('Y-m-d H:i:s'),
$errno,
$errstr,
$errfile,
$errline,
$_SERVER['REQUEST_URI'] ?? 'CLI',
$_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'
);
file_put_contents('production_errors.log', $logEntry . PHP_EOL, FILE_APPEND);
// 发送邮件通知管理员(仅对严重错误)
if ($errno === E_ERROR || $errno === E_USER_ERROR) {
error_log("严重错误: $logEntry", 1, 'admin@example.com');
}
// 显示友好的错误页面给用户
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
include 'error_pages/500.html';
}
return true;
}
// 设置环境
setProductionEnvironment();
set_error_handler('productionErrorHandler');
?>
常见错误排查技巧
1. 启用详细错误报告
<?php
// 临时启用所有错误报告
ini_set('display_errors', 1);
error_reporting(E_ALL);
// 或者使用
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(-1); // -1 表示所有错误
?>
2. 检查语法错误
<?php
// 使用命令行检查语法
// php -l filename.php
// 在代码中检查语法
function checkSyntax($code) {
$tempFile = tempnam(sys_get_temp_dir(), 'syntax_check');
file_put_contents($tempFile, "<?php\n" . $code);
$output = [];
$returnCode = 0;
exec("php -l $tempFile 2>&1", $output, $returnCode);
unlink($tempFile);
return $returnCode === 0;
}
// 使用示例
$code = 'echo "Hello World"'; // 缺少分号
if (!checkSyntax($code)) {
echo "代码存在语法错误\n";
}
?>
3. 使用var_dump()调试
<?php
function debugVariable($var, $label = '') {
echo "<pre>";
if ($label) {
echo "$label: ";
}
var_dump($var);
echo "</pre>";
}
function debugArray($array) {
echo "<pre>";
print_r($array);
echo "</pre>";
}
// 使用示例
$user = ['name' => 'John', 'age' => 30, 'email' => 'john@example.com'];
debugVariable($user, '用户信息');
debugArray($user);
?>
错误预防策略
1. 输入验证
<?php
function validateInput($data, $rules) {
$errors = [];
foreach ($rules as $field => $rule) {
$value = $data[$field] ?? null;
// 必填验证
if (isset($rule['required']) && $rule['required'] && empty($value)) {
$errors[$field] = "$field 是必填字段";
continue;
}
// 类型验证
if (isset($rule['type']) && $value !== null) {
switch ($rule['type']) {
case 'email':
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field] = "$field 必须是有效的邮箱地址";
}
break;
case 'int':
if (!filter_var($value, FILTER_VALIDATE_INT)) {
$errors[$field] = "$field 必须是整数";
}
break;
case 'string':
if (!is_string($value)) {
$errors[$field] = "$field 必须是字符串";
}
break;
}
}
// 长度验证
if (isset($rule['min_length']) && strlen($value) < $rule['min_length']) {
$errors[$field] = "$field 长度不能少于 {$rule['min_length']} 个字符";
}
if (isset($rule['max_length']) && strlen($value) > $rule['max_length']) {
$errors[$field] = "$field 长度不能超过 {$rule['max_length']} 个字符";
}
}
return $errors;
}
// 使用示例
$userData = [
'name' => 'Jo',
'email' => 'invalid-email',
'age' => 'not_a_number'
];
$rules = [
'name' => [
'required' => true,
'type' => 'string',
'min_length' => 2,
'max_length' => 50
],
'email' => [
'required' => true,
'type' => 'email'
],
'age' => [
'type' => 'int'
]
];
$errors = validateInput($userData, $rules);
if (!empty($errors)) {
echo "验证失败:\n";
print_r($errors);
}
?>
2. 使用isset()检查变量
<?php
// 安全的数组访问
function safeArrayAccess($array, $key, $default = null) {
return isset($array[$key]) ? $array[$key] : $default;
}
// 安全的对象属性访问
function safePropertyAccess($object, $property, $default = null) {
return isset($object->$property) ? $object->$property : $default;
}
// 使用示例
$config = [
'database' => [
'host' => 'localhost',
'port' => 3306
]
];
$dbHost = safeArrayAccess($config, 'database')['host'] ?? 'default_host';
$dbPort = safeArrayAccess($config, 'database', ['port' => 'default_port'])['port'];
$unknown = safeArrayAccess($config, 'unknown', 'default_value');
?>
3. 使用三元运算符和null合并操作符
<?php
// 传统方式
if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = 1;
}
// 使用三元运算符
$page = isset($_GET['page']) ? $_GET['page'] : 1;
// 使用null合并操作符(PHP 7+)
$page = $_GET['page'] ?? 1;
// 链式null合并操作符
$user = $_GET['user'] ?? $_SESSION['user'] ?? $defaultUser;
// 安全的数组访问
$config = $config['database']['host'] ?? 'localhost';
?>
通过本节的学习,你应该了解了PHP中不同类型的错误、如何配置错误报告、以及如何处理和预防错误。理解这些概念对于编写健壮、可靠的PHP应用程序至关重要。下一节我们将学习更高级的异常处理机制。