PHP错误处理与调试技巧:从新手到专家的必备技能
Orion K Lv6

PHP错误处理与调试技巧:从新手到专家的必备技能

错误处理和调试是PHP开发中不可避免的重要环节。作为一名有多年开发经验的程序员,我深知良好的错误处理机制对于构建稳定应用的重要性。今天我想分享一些关于PHP错误处理和调试的实用技巧。

PHP错误类型详解

1. 错误级别分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
// PHP错误级别演示
error_reporting(E_ALL); // 显示所有错误
ini_set('display_errors', 1); // 在页面上显示错误

echo "=== PHP错误级别演示 ===\n";

// E_ERROR - 致命错误(脚本停止执行)
// 示例:调用不存在的函数
// undefinedFunction(); // 这会产生致命错误

// E_WARNING - 警告(脚本继续执行)
// 示例:包含不存在的文件
// include 'nonexistent.php'; // 这会产生警告

// E_NOTICE - 注意(脚本继续执行)
// 示例:使用未定义的变量
echo "未定义变量:$undefinedVar\n"; // 这会产生注意

// E_PARSE - 解析错误
// 示例:语法错误
// echo "缺少分号" // 这会产生解析错误

// E_STRICT - 严格标准
// 示例:使用已弃用的功能

// E_DEPRECATED - 已弃用
// 示例:使用已弃用的函数

// 自定义错误级别
$errorLevels = [
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED'
];

echo "PHP错误级别常量:\n";
foreach ($errorLevels as $level => $name) {
echo "$name: $level\n";
}
?>

2. 错误报告配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
// 错误报告配置示例
class ErrorConfig {
public static function developmentMode() {
// 开发环境:显示所有错误
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('log_errors', 1);
ini_set('error_log', 'php_errors.log');

echo "开发模式:显示所有错误\n";
}

public static function productionMode() {
// 生产环境:隐藏错误,只记录日志
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php/error.log');

echo "生产模式:隐藏错误显示,记录到日志\n";
}

public static function testingMode() {
// 测试环境:记录所有错误但不显示
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', 'test_errors.log');

echo "测试模式:记录错误但不显示\n";
}

public static function getCurrentSettings() {
echo "当前错误配置:\n";
echo "error_reporting: " . error_reporting() . "\n";
echo "display_errors: " . ini_get('display_errors') . "\n";
echo "log_errors: " . ini_get('log_errors') . "\n";
echo "error_log: " . ini_get('error_log') . "\n";
}
}

// 根据环境设置错误报告
$environment = 'development'; // 可以是 development, production, testing

switch ($environment) {
case 'development':
ErrorConfig::developmentMode();
break;
case 'production':
ErrorConfig::productionMode();
break;
case 'testing':
ErrorConfig::testingMode();
break;
}

ErrorConfig::getCurrentSettings();
?>

自定义错误处理

1. 自定义错误处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
// 自定义错误处理函数
function customErrorHandler($errno, $errstr, $errfile, $errline) {
$errorTypes = [
E_ERROR => 'Error',
E_WARNING => 'Warning',
E_PARSE => 'Parse Error',
E_NOTICE => 'Notice',
E_CORE_ERROR => 'Core Error',
E_CORE_WARNING => 'Core Warning',
E_COMPILE_ERROR => 'Compile Error',
E_COMPILE_WARNING => 'Compile Warning',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
E_STRICT => 'Strict Notice',
E_RECOVERABLE_ERROR => 'Recoverable Error',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated'
];

$errorType = $errorTypes[$errno] ?? 'Unknown Error';
$timestamp = date('Y-m-d H:i:s');

// 格式化错误信息
$errorMessage = "[$timestamp] $errorType: $errstr in $errfile on line $errline\n";

// 记录到日志文件
error_log($errorMessage, 3, 'custom_errors.log');

// 根据错误级别决定是否显示
if ($errno & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)) {
echo "致命错误:$errstr\n";
echo "文件:$errfile,行号:$errline\n";

// 对于致命错误,可以发送邮件通知
// mail('admin@example.com', 'PHP Fatal Error', $errorMessage);

exit(1);
} elseif ($errno & (E_WARNING | E_USER_WARNING)) {
echo "警告:$errstr\n";
} elseif ($errno & (E_NOTICE | E_USER_NOTICE)) {
// 注意级别的错误通常不显示给用户
// 只记录到日志
}

// 返回true表示错误已处理,不会传递给PHP默认错误处理器
return true;
}

// 设置自定义错误处理函数
set_error_handler('customErrorHandler');

// 测试不同级别的错误
echo "测试自定义错误处理:\n";

// 触发用户警告
trigger_error("这是一个用户警告", E_USER_WARNING);

// 触发用户注意
trigger_error("这是一个用户注意", E_USER_NOTICE);

// 访问未定义变量(产生E_NOTICE)
echo "未定义变量:$undefinedVariable\n";

// 恢复默认错误处理器
restore_error_handler();
?>

2. 错误处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<?php
class ErrorHandler {
private $logFile;
private $emailNotification;
private $displayErrors;

public function __construct($logFile = 'app_errors.log', $emailNotification = false, $displayErrors = true) {
$this->logFile = $logFile;
$this->emailNotification = $emailNotification;
$this->displayErrors = $displayErrors;

// 注册错误处理函数
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
register_shutdown_function([$this, 'handleFatalError']);
}

public function handleError($errno, $errstr, $errfile, $errline) {
$errorInfo = [
'type' => 'Error',
'level' => $errno,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'timestamp' => date('Y-m-d H:i:s'),
'trace' => debug_backtrace()
];

$this->logError($errorInfo);

if ($this->shouldDisplay($errno)) {
$this->displayError($errorInfo);
}

if ($this->shouldNotify($errno)) {
$this->sendNotification($errorInfo);
}

return true;
}

public function handleException($exception) {
$errorInfo = [
'type' => 'Exception',
'level' => E_ERROR,
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'timestamp' => date('Y-m-d H:i:s'),
'trace' => $exception->getTrace(),
'class' => get_class($exception)
];

$this->logError($errorInfo);
$this->displayError($errorInfo);
$this->sendNotification($errorInfo);
}

public function handleFatalError() {
$error = error_get_last();

if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
$errorInfo = [
'type' => 'Fatal Error',
'level' => $error['type'],
'message' => $error['message'],
'file' => $error['file'],
'line' => $error['line'],
'timestamp' => date('Y-m-d H:i:s')
];

$this->logError($errorInfo);
$this->sendNotification($errorInfo);

// 显示友好的错误页面
if ($this->displayErrors) {
echo "系统遇到了一个严重错误,请稍后再试。\n";
}
}
}

private function logError($errorInfo) {
$logEntry = sprintf(
"[%s] %s: %s in %s on line %d\n",
$errorInfo['timestamp'],
$errorInfo['type'],
$errorInfo['message'],
$errorInfo['file'],
$errorInfo['line']
);

// 添加堆栈跟踪
if (isset($errorInfo['trace'])) {
$logEntry .= "Stack trace:\n";
foreach ($errorInfo['trace'] as $i => $trace) {
$file = $trace['file'] ?? 'unknown';
$line = $trace['line'] ?? 'unknown';
$function = $trace['function'] ?? 'unknown';
$class = isset($trace['class']) ? $trace['class'] . '::' : '';

$logEntry .= sprintf("#%d %s(%s): %s%s()\n", $i, $file, $line, $class, $function);
}
}

$logEntry .= str_repeat('-', 80) . "\n";

file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
}

private function displayError($errorInfo) {
if (!$this->displayErrors) {
return;
}

echo "错误类型:{$errorInfo['type']}\n";
echo "错误信息:{$errorInfo['message']}\n";
echo "文件位置:{$errorInfo['file']}:{$errorInfo['line']}\n";
echo "发生时间:{$errorInfo['timestamp']}\n";
echo str_repeat('-', 50) . "\n";
}

private function shouldDisplay($errno) {
return $this->displayErrors && ($errno & error_reporting());
}

private function shouldNotify($errno) {
return $this->emailNotification &&
($errno & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR));
}

private function sendNotification($errorInfo) {
if (!$this->emailNotification) {
return;
}

$subject = "PHP Error Notification - {$errorInfo['type']}";
$message = "An error occurred in your PHP application:\n\n";
$message .= "Type: {$errorInfo['type']}\n";
$message .= "Message: {$errorInfo['message']}\n";
$message .= "File: {$errorInfo['file']}\n";
$message .= "Line: {$errorInfo['line']}\n";
$message .= "Time: {$errorInfo['timestamp']}\n";

// 这里应该使用实际的邮件发送功能
echo "邮件通知:$subject\n$message\n";
}

public function getErrorLog($lines = 50) {
if (!file_exists($this->logFile)) {
return [];
}

$file = new SplFileObject($this->logFile);
$file->seek(PHP_INT_MAX);
$totalLines = $file->key();

$startLine = max(0, $totalLines - $lines);
$logs = [];

$file->seek($startLine);
while (!$file->eof()) {
$line = trim($file->current());
if (!empty($line)) {
$logs[] = $line;
}
$file->next();
}

return $logs;
}
}

// 使用错误处理类
$errorHandler = new ErrorHandler('application_errors.log', false, true);

// 测试错误处理
echo "测试错误处理类:\n";

try {
// 触发一个警告
$result = 10 / 0;
} catch (DivisionByZeroError $e) {
echo "捕获到除零错误\n";
}

// 触发用户错误
trigger_error("这是一个测试错误", E_USER_ERROR);
?>

异常处理

1. 基本异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
// 基本异常处理示例
echo "=== 基本异常处理 ===\n";

// 抛出和捕获异常
function divide($a, $b) {
if ($b == 0) {
throw new InvalidArgumentException("除数不能为零");
}
return $a / $b;
}

try {
$result = divide(10, 2);
echo "10 ÷ 2 = $result\n";

$result = divide(10, 0); // 这会抛出异常
echo "这行不会执行\n";

} catch (InvalidArgumentException $e) {
echo "捕获到异常:" . $e->getMessage() . "\n";
echo "异常文件:" . $e->getFile() . "\n";
echo "异常行号:" . $e->getLine() . "\n";
} finally {
echo "finally块总是会执行\n";
}

// 多个catch块
function processData($data) {
if (!is_array($data)) {
throw new InvalidArgumentException("数据必须是数组");
}

if (empty($data)) {
throw new RuntimeException("数据不能为空");
}

if (count($data) > 1000) {
throw new OverflowException("数据量过大");
}

return array_sum($data);
}

try {
$result = processData("not an array");
} catch (InvalidArgumentException $e) {
echo "参数错误:" . $e->getMessage() . "\n";
} catch (RuntimeException $e) {
echo "运行时错误:" . $e->getMessage() . "\n";
} catch (OverflowException $e) {
echo "溢出错误:" . $e->getMessage() . "\n";
} catch (Exception $e) {
echo "其他异常:" . $e->getMessage() . "\n";
}
?>

2. 自定义异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<?php
// 自定义异常类
class DatabaseException extends Exception {
private $query;

public function __construct($message, $query = '', $code = 0, Exception $previous = null) {
parent::__construct($message, $code, $previous);
$this->query = $query;
}

public function getQuery() {
return $this->query;
}

public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message} (Query: {$this->query})\n";
}
}

class ValidationException extends Exception {
private $errors = [];

public function __construct($message, array $errors = [], $code = 0, Exception $previous = null) {
parent::__construct($message, $code, $previous);
$this->errors = $errors;
}

public function getErrors() {
return $this->errors;
}

public function addError($field, $message) {
$this->errors[$field] = $message;
}
}

class FileOperationException extends Exception {
private $filename;
private $operation;

public function __construct($message, $filename = '', $operation = '', $code = 0, Exception $previous = null) {
parent::__construct($message, $code, $previous);
$this->filename = $filename;
$this->operation = $operation;
}

public function getFilename() {
return $this->filename;
}

public function getOperation() {
return $this->operation;
}
}

// 使用自定义异常
class UserService {
public function validateUser($userData) {
$errors = [];

if (empty($userData['name'])) {
$errors['name'] = '姓名不能为空';
}

if (empty($userData['email']) || !filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = '邮箱格式不正确';
}

if (!empty($errors)) {
throw new ValidationException('用户数据验证失败', $errors);
}

return true;
}

public function saveUser($userData) {
try {
$this->validateUser($userData);

// 模拟数据库操作
$query = "INSERT INTO users (name, email) VALUES (?, ?)";
$success = rand(0, 1); // 随机成功或失败

if (!$success) {
throw new DatabaseException('用户保存失败', $query, 1062);
}

return true;

} catch (ValidationException $e) {
echo "验证错误:\n";
foreach ($e->getErrors() as $field => $error) {
echo " $field: $error\n";
}
throw $e; // 重新抛出异常
} catch (DatabaseException $e) {
echo "数据库错误:" . $e->getMessage() . "\n";
echo "查询语句:" . $e->getQuery() . "\n";
throw $e;
}
}
}

// 测试自定义异常
$userService = new UserService();

try {
$userData = [
'name' => '',
'email' => 'invalid-email'
];

$userService->saveUser($userData);

} catch (ValidationException $e) {
echo "最终捕获验证异常\n";
} catch (DatabaseException $e) {
echo "最终捕获数据库异常\n";
} catch (Exception $e) {
echo "最终捕获其他异常:" . $e->getMessage() . "\n";
}
?>

调试技巧

1. 基本调试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?php
// 基本调试方法
echo "=== 基本调试方法 ===\n";

$user = [
'id' => 1,
'name' => '张三',
'email' => 'zhangsan@example.com',
'roles' => ['user', 'admin']
];

// 1. var_dump() - 显示详细的变量信息
echo "使用var_dump():\n";
var_dump($user);

// 2. print_r() - 更易读的格式
echo "\n使用print_r():\n";
print_r($user);

// 3. var_export() - 输出可执行的PHP代码
echo "\n使用var_export():\n";
var_export($user);
echo "\n";

// 4. json_encode() - JSON格式输出
echo "\n使用json_encode():\n";
echo json_encode($user, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";

// 5. 调试函数封装
function debug($var, $label = '') {
$trace = debug_backtrace();
$caller = $trace[0];

echo "\n" . str_repeat('=', 50) . "\n";
echo "DEBUG" . ($label ? " - $label" : '') . "\n";
echo "文件:{$caller['file']}\n";
echo "行号:{$caller['line']}\n";
echo str_repeat('-', 50) . "\n";

if (is_array($var) || is_object($var)) {
print_r($var);
} else {
var_dump($var);
}

echo str_repeat('=', 50) . "\n";
}

debug($user, '用户信息');

// 6. 条件调试
$debugMode = true;
if ($debugMode) {
debug($user['roles'], '用户角色');
}

// 7. 调试到文件
function debugToFile($var, $filename = 'debug.log', $label = '') {
$trace = debug_backtrace();
$caller = $trace[0];

$output = "\n" . date('Y-m-d H:i:s') . " - DEBUG" . ($label ? " - $label" : '') . "\n";
$output .= "文件:{$caller['file']}:{$caller['line']}\n";
$output .= str_repeat('-', 50) . "\n";

ob_start();
if (is_array($var) || is_object($var)) {
print_r($var);
} else {
var_dump($var);
}
$output .= ob_get_clean();

$output .= str_repeat('=', 50) . "\n";

file_put_contents($filename, $output, FILE_APPEND | LOCK_EX);
}

debugToFile($user, 'app_debug.log', '用户数据');
?>

2. 高级调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
// 高级调试技巧
class AdvancedDebugger {
private static $startTime;
private static $memoryStart;
private static $queries = [];

public static function start() {
self::$startTime = microtime(true);
self::$memoryStart = memory_get_usage();

echo "调试开始 - " . date('Y-m-d H:i:s') . "\n";
echo "初始内存使用:" . self::formatBytes(self::$memoryStart) . "\n";
echo str_repeat('-', 50) . "\n";
}

public static function checkpoint($label = '') {
$currentTime = microtime(true);
$currentMemory = memory_get_usage();

$timeElapsed = $currentTime - self::$startTime;
$memoryUsed = $currentMemory - self::$memoryStart;

echo "检查点" . ($label ? " - $label" : '') . "\n";
echo "执行时间:" . number_format($timeElapsed, 4) . "秒\n";
echo "内存使用:" . self::formatBytes($memoryUsed) . "\n";
echo "当前内存:" . self::formatBytes($currentMemory) . "\n";
echo "峰值内存:" . self::formatBytes(memory_get_peak_usage()) . "\n";
echo str_repeat('-', 30) . "\n";
}

public static function traceFunction($function, $args = []) {
echo "调用函数:$function\n";
echo "参数:" . json_encode($args) . "\n";

$startTime = microtime(true);
$startMemory = memory_get_usage();

// 这里应该调用实际的函数
// $result = call_user_func_array($function, $args);

$endTime = microtime(true);
$endMemory = memory_get_usage();

echo "执行时间:" . number_format($endTime - $startTime, 4) . "秒\n";
echo "内存变化:" . self::formatBytes($endMemory - $startMemory) . "\n";
echo str_repeat('-', 30) . "\n";
}

public static function dumpBacktrace($limit = 10) {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit);

echo "调用堆栈:\n";
foreach ($trace as $i => $call) {
$file = $call['file'] ?? 'unknown';
$line = $call['line'] ?? 'unknown';
$function = $call['function'] ?? 'unknown';
$class = isset($call['class']) ? $call['class'] . '::' : '';

echo "#$i $file($line): {$class}$function()\n";
}
echo str_repeat('-', 50) . "\n";
}

public static function logQuery($query, $params = [], $executionTime = 0) {
self::$queries[] = [
'query' => $query,
'params' => $params,
'time' => $executionTime,
'timestamp' => microtime(true)
];
}

public static function dumpQueries() {
echo "数据库查询记录:\n";
$totalTime = 0;

foreach (self::$queries as $i => $query) {
echo "#" . ($i + 1) . " [{$query['time']}s] {$query['query']}\n";
if (!empty($query['params'])) {
echo " 参数:" . json_encode($query['params']) . "\n";
}
$totalTime += $query['time'];
}

echo "总查询数:" . count(self::$queries) . "\n";
echo "总查询时间:{$totalTime}s\n";
echo str_repeat('-', 50) . "\n";
}

private static function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= pow(1024, $pow);

return round($bytes, 2) . ' ' . $units[$pow];
}

public static function end() {
$endTime = microtime(true);
$endMemory = memory_get_usage();

$totalTime = $endTime - self::$startTime;
$totalMemory = $endMemory - self::$memoryStart;

echo "调试结束\n";
echo "总执行时间:" . number_format($totalTime, 4) . "秒\n";
echo "总内存使用:" . self::formatBytes($totalMemory) . "\n";
echo "峰值内存:" . self::formatBytes(memory_get_peak_usage()) . "\n";
echo str_repeat('=', 50) . "\n";
}
}

// 使用高级调试器
AdvancedDebugger::start();

// 模拟一些操作
$data = range(1, 10000);
AdvancedDebugger::checkpoint('创建数组');

$sum = array_sum($data);
AdvancedDebugger::checkpoint('计算数组和');

// 模拟数据库查询
AdvancedDebugger::logQuery('SELECT * FROM users WHERE id = ?', [1], 0.0023);
AdvancedDebugger::logQuery('UPDATE users SET last_login = NOW() WHERE id = ?', [1], 0.0015);

AdvancedDebugger::dumpQueries();
AdvancedDebugger::dumpBacktrace(5);
AdvancedDebugger::end();
?>

3. 性能分析和优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
// 性能分析工具
class PerformanceProfiler {
private static $timers = [];
private static $counters = [];
private static $memorySnapshots = [];

public static function startTimer($name) {
self::$timers[$name] = [
'start' => microtime(true),
'memory_start' => memory_get_usage()
];
}

public static function endTimer($name) {
if (!isset(self::$timers[$name])) {
echo "警告:计时器 '$name' 未启动\n";
return;
}

$timer = self::$timers[$name];
$endTime = microtime(true);
$endMemory = memory_get_usage();

$duration = $endTime - $timer['start'];
$memoryUsed = $endMemory - $timer['memory_start'];

echo "性能分析 - $name:\n";
echo " 执行时间: " . number_format($duration, 4) . "秒\n";
echo " 内存使用: " . self::formatBytes($memoryUsed) . "\n";
echo " 开始内存: " . self::formatBytes($timer['memory_start']) . "\n";
echo " 结束内存: " . self::formatBytes($endMemory) . "\n";
echo str_repeat('-', 40) . "\n";

unset(self::$timers[$name]);

return $duration;
}

public static function incrementCounter($name, $value = 1) {
if (!isset(self::$counters[$name])) {
self::$counters[$name] = 0;
}
self::$counters[$name] += $value;
}

public static function snapshot($name) {
self::$memorySnapshots[$name] = [
'memory' => memory_get_usage(),
'peak' => memory_get_peak_usage(),
'time' => microtime(true)
];
}

public static function compareSnapshots($name1, $name2) {
if (!isset(self::$memorySnapshots[$name1]) || !isset(self::$memorySnapshots[$name2])) {
echo "错误:快照不存在\n";
return;
}

$snap1 = self::$memorySnapshots[$name1];
$snap2 = self::$memorySnapshots[$name2];

$memoryDiff = $snap2['memory'] - $snap1['memory'];
$timeDiff = $snap2['time'] - $snap1['time'];

echo "快照比较 ($name1 -> $name2):\n";
echo " 时间差: " . number_format($timeDiff, 4) . "秒\n";
echo " 内存差: " . self::formatBytes($memoryDiff) . "\n";
echo str_repeat('-', 40) . "\n";
}

public static function getReport() {
echo "=== 性能报告 ===\n";

if (!empty(self::$counters)) {
echo "计数器:\n";
foreach (self::$counters as $name => $count) {
echo " $name: $count\n";
}
echo "\n";
}

if (!empty(self::$memorySnapshots)) {
echo "内存快照:\n";
foreach (self::$memorySnapshots as $name => $snapshot) {
echo " $name: " . self::formatBytes($snapshot['memory']) .
" (峰值: " . self::formatBytes($snapshot['peak']) . ")\n";
}
echo "\n";
}

echo "当前内存使用: " . self::formatBytes(memory_get_usage()) . "\n";
echo "峰值内存使用: " . self::formatBytes(memory_get_peak_usage()) . "\n";
echo str_repeat('=', 50) . "\n";
}

private static function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= pow(1024, $pow);
return round($bytes, 2) . ' ' . $units[$pow];
}
}

// 使用性能分析器
PerformanceProfiler::snapshot('开始');

PerformanceProfiler::startTimer('数组操作');
$largeArray = range(1, 100000);
$sum = 0;
foreach ($largeArray as $value) {
$sum += $value;
PerformanceProfiler::incrementCounter('循环次数');
}
PerformanceProfiler::endTimer('数组操作');

PerformanceProfiler::snapshot('数组操作后');

PerformanceProfiler::startTimer('字符串操作');
$longString = str_repeat('Hello World! ', 10000);
$words = explode(' ', $longString);
PerformanceProfiler::incrementCounter('单词数', count($words));
PerformanceProfiler::endTimer('字符串操作');

PerformanceProfiler::snapshot('字符串操作后');

PerformanceProfiler::compareSnapshots('开始', '数组操作后');
PerformanceProfiler::compareSnapshots('数组操作后', '字符串操作后');
PerformanceProfiler::getReport();
?>

日志记录系统

1. 完整的日志系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
<?php
// 完整的日志记录系统
class Logger {
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';

private $logFile;
private $logLevel;
private $dateFormat;
private $maxFileSize;
private $maxFiles;

private static $levels = [
self::EMERGENCY => 0,
self::ALERT => 1,
self::CRITICAL => 2,
self::ERROR => 3,
self::WARNING => 4,
self::NOTICE => 5,
self::INFO => 6,
self::DEBUG => 7
];

public function __construct($logFile = 'app.log', $logLevel = self::INFO, $dateFormat = 'Y-m-d H:i:s') {
$this->logFile = $logFile;
$this->logLevel = $logLevel;
$this->dateFormat = $dateFormat;
$this->maxFileSize = 10 * 1024 * 1024; // 10MB
$this->maxFiles = 5;

// 确保日志目录存在
$logDir = dirname($this->logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
}

public function log($level, $message, array $context = []) {
if (!$this->shouldLog($level)) {
return;
}

$this->rotateLogIfNeeded();

$timestamp = date($this->dateFormat);
$levelUpper = strtoupper($level);

// 获取调用者信息
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1] ?? $backtrace[0];
$file = basename($caller['file'] ?? 'unknown');
$line = $caller['line'] ?? 'unknown';

// 处理上下文信息
$contextStr = '';
if (!empty($context)) {
$contextStr = ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);
}

// 格式化日志条目
$logEntry = sprintf(
"[%s] %s: %s (%s:%s)%s\n",
$timestamp,
$levelUpper,
$message,
$file,
$line,
$contextStr
);

// 写入日志文件
file_put_contents($this->logFile, $logEntry, FILE_APPEND | LOCK_EX);
}

public function emergency($message, array $context = []) {
$this->log(self::EMERGENCY, $message, $context);
}

public function alert($message, array $context = []) {
$this->log(self::ALERT, $message, $context);
}

public function critical($message, array $context = []) {
$this->log(self::CRITICAL, $message, $context);
}

public function error($message, array $context = []) {
$this->log(self::ERROR, $message, $context);
}

public function warning($message, array $context = []) {
$this->log(self::WARNING, $message, $context);
}

public function notice($message, array $context = []) {
$this->log(self::NOTICE, $message, $context);
}

public function info($message, array $context = []) {
$this->log(self::INFO, $message, $context);
}

public function debug($message, array $context = []) {
$this->log(self::DEBUG, $message, $context);
}

private function shouldLog($level) {
return self::$levels[$level] <= self::$levels[$this->logLevel];
}

private function rotateLogIfNeeded() {
if (!file_exists($this->logFile)) {
return;
}

if (filesize($this->logFile) < $this->maxFileSize) {
return;
}

// 轮转日志文件
for ($i = $this->maxFiles - 1; $i > 0; $i--) {
$oldFile = $this->logFile . '.' . $i;
$newFile = $this->logFile . '.' . ($i + 1);

if (file_exists($oldFile)) {
if ($i == $this->maxFiles - 1) {
unlink($oldFile); // 删除最老的文件
} else {
rename($oldFile, $newFile);
}
}
}

// 重命名当前日志文件
rename($this->logFile, $this->logFile . '.1');
}

public function tail($lines = 50) {
if (!file_exists($this->logFile)) {
return [];
}

$file = new SplFileObject($this->logFile);
$file->seek(PHP_INT_MAX);
$totalLines = $file->key();

$startLine = max(0, $totalLines - $lines);
$logs = [];

$file->seek($startLine);
while (!$file->eof()) {
$line = trim($file->current());
if (!empty($line)) {
$logs[] = $line;
}
$file->next();
}

return $logs;
}

public function search($pattern, $maxResults = 100) {
if (!file_exists($this->logFile)) {
return [];
}

$results = [];
$count = 0;

$handle = fopen($this->logFile, 'r');
if ($handle) {
while (($line = fgets($handle)) !== false && $count < $maxResults) {
if (preg_match($pattern, $line)) {
$results[] = trim($line);
$count++;
}
}
fclose($handle);
}

return $results;
}
}

// 使用日志系统
$logger = new Logger('logs/application.log', Logger::DEBUG);

$logger->info('应用程序启动', ['version' => '1.0.0', 'environment' => 'development']);
$logger->debug('调试信息', ['user_id' => 123, 'action' => 'login']);
$logger->warning('警告信息', ['memory_usage' => '85%']);
$logger->error('错误信息', ['error_code' => 500, 'message' => '数据库连接失败']);

// 查看最近的日志
echo "最近的日志记录:\n";
$recentLogs = $logger->tail(5);
foreach ($recentLogs as $log) {
echo $log . "\n";
}

// 搜索错误日志
echo "\n错误日志:\n";
$errorLogs = $logger->search('/ERROR:/', 10);
foreach ($errorLogs as $log) {
echo $log . "\n";
}
?>

实用调试工具

1. 调试工具集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<?php
// 实用调试工具集合
class DebugToolkit {
private static $enabled = true;
private static $output = 'screen'; // screen, file, both
private static $logFile = 'debug_toolkit.log';

public static function enable($output = 'screen', $logFile = 'debug_toolkit.log') {
self::$enabled = true;
self::$output = $output;
self::$logFile = $logFile;
}

public static function disable() {
self::$enabled = false;
}

public static function dump($var, $label = '', $return = false) {
if (!self::$enabled) {
return;
}

$trace = debug_backtrace();
$caller = $trace[0];

$output = "\n" . str_repeat('=', 60) . "\n";
$output .= "DEBUG DUMP" . ($label ? " - $label" : '') . "\n";
$output .= "Time: " . date('Y-m-d H:i:s') . "\n";
$output .= "File: {$caller['file']}:{$caller['line']}\n";
$output .= str_repeat('-', 60) . "\n";

ob_start();
if (is_array($var) || is_object($var)) {
print_r($var);
} else {
var_dump($var);
}
$output .= ob_get_clean();

$output .= str_repeat('=', 60) . "\n";

if ($return) {
return $output;
}

self::output($output);
}

public static function trace($limit = 10, $label = '') {
if (!self::$enabled) {
return;
}

$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit + 1);
array_shift($trace); // 移除当前函数

$output = "\n" . str_repeat('=', 60) . "\n";
$output .= "STACK TRACE" . ($label ? " - $label" : '') . "\n";
$output .= "Time: " . date('Y-m-d H:i:s') . "\n";
$output .= str_repeat('-', 60) . "\n";

foreach ($trace as $i => $call) {
$file = $call['file'] ?? 'unknown';
$line = $call['line'] ?? 'unknown';
$function = $call['function'] ?? 'unknown';
$class = isset($call['class']) ? $call['class'] . '::' : '';

$output .= sprintf("#%d %s(%s): %s%s()\n", $i, $file, $line, $class, $function);
}

$output .= str_repeat('=', 60) . "\n";

self::output($output);
}

public static function benchmark($callback, $iterations = 1, $label = '') {
if (!self::$enabled) {
return call_user_func($callback);
}

$startTime = microtime(true);
$startMemory = memory_get_usage();

$result = null;
for ($i = 0; $i < $iterations; $i++) {
$result = call_user_func($callback);
}

$endTime = microtime(true);
$endMemory = memory_get_usage();

$totalTime = $endTime - $startTime;
$avgTime = $totalTime / $iterations;
$memoryUsed = $endMemory - $startMemory;

$output = "\n" . str_repeat('=', 60) . "\n";
$output .= "BENCHMARK" . ($label ? " - $label" : '') . "\n";
$output .= "Time: " . date('Y-m-d H:i:s') . "\n";
$output .= str_repeat('-', 60) . "\n";
$output .= "Iterations: $iterations\n";
$output .= "Total time: " . number_format($totalTime, 6) . "s\n";
$output .= "Average time: " . number_format($avgTime, 6) . "s\n";
$output .= "Memory used: " . self::formatBytes($memoryUsed) . "\n";
$output .= "Peak memory: " . self::formatBytes(memory_get_peak_usage()) . "\n";
$output .= str_repeat('=', 60) . "\n";

self::output($output);

return $result;
}

public static function assert($condition, $message = 'Assertion failed', $data = null) {
if (!self::$enabled) {
return;
}

if (!$condition) {
$trace = debug_backtrace();
$caller = $trace[0];

$output = "\n" . str_repeat('!', 60) . "\n";
$output .= "ASSERTION FAILED\n";
$output .= "Time: " . date('Y-m-d H:i:s') . "\n";
$output .= "File: {$caller['file']}:{$caller['line']}\n";
$output .= "Message: $message\n";

if ($data !== null) {
$output .= "Data: " . json_encode($data, JSON_PRETTY_PRINT) . "\n";
}

$output .= str_repeat('!', 60) . "\n";

self::output($output);
}
}

public static function watch($var, $name = '') {
static $watchList = [];

if (!self::$enabled) {
return;
}

$trace = debug_backtrace();
$caller = $trace[0];
$key = $caller['file'] . ':' . $caller['line'] . ($name ? ":$name" : '');

$currentValue = serialize($var);

if (isset($watchList[$key])) {
if ($watchList[$key] !== $currentValue) {
$output = "\n" . str_repeat('~', 60) . "\n";
$output .= "VARIABLE CHANGED" . ($name ? " - $name" : '') . "\n";
$output .= "Time: " . date('Y-m-d H:i:s') . "\n";
$output .= "Location: {$caller['file']}:{$caller['line']}\n";
$output .= str_repeat('-', 60) . "\n";
$output .= "Old value:\n";
$output .= print_r(unserialize($watchList[$key]), true);
$output .= "New value:\n";
$output .= print_r($var, true);
$output .= str_repeat('~', 60) . "\n";

self::output($output);
}
}

$watchList[$key] = $currentValue;
}

private static function output($content) {
if (self::$output === 'screen' || self::$output === 'both') {
echo $content;
}

if (self::$output === 'file' || self::$output === 'both') {
file_put_contents(self::$logFile, $content, FILE_APPEND | LOCK_EX);
}
}

private static function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= pow(1024, $pow);
return round($bytes, 2) . ' ' . $units[$pow];
}
}

// 使用调试工具集
DebugToolkit::enable('both', 'debug_output.log');

$testData = ['name' => '张三', 'age' => 25];
DebugToolkit::dump($testData, '用户数据');

DebugToolkit::trace(5, '调用堆栈');

// 性能测试
$result = DebugToolkit::benchmark(function() {
$sum = 0;
for ($i = 0; $i < 10000; $i++) {
$sum += $i;
}
return $sum;
}, 10, '循环性能测试');

// 断言测试
DebugToolkit::assert($result > 0, '结果应该大于0', ['result' => $result]);
DebugToolkit::assert($result < 0, '这个断言会失败', ['result' => $result]);

// 变量监视
$counter = 0;
DebugToolkit::watch($counter, '计数器');

$counter = 10;
DebugToolkit::watch($counter, '计数器'); // 这会触发变化通知

$counter = 20;
DebugToolkit::watch($counter, '计数器'); // 这也会触发变化通知
?>

总结

PHP错误处理与调试的关键要点:

  1. 错误类型理解

    • 掌握不同错误级别的含义
    • 合理配置错误报告级别
    • 区分开发和生产环境的错误处理
  2. 自定义错误处理

    • 实现自定义错误处理函数
    • 创建错误处理类
    • 处理致命错误和异常
  3. 异常处理机制

    • 正确使用try-catch-finally
    • 创建自定义异常类
    • 实现异常链和上下文信息
  4. 调试技巧

    • 熟练使用var_dump、print_r等调试函数
    • 掌握堆栈跟踪和性能分析
    • 使用高级调试工具
  5. 日志记录系统

    • 实现完整的日志记录功能
    • 支持日志轮转和搜索
    • 合理设置日志级别
  6. 性能监控

    • 监控执行时间和内存使用
    • 实现性能基准测试
    • 分析和优化瓶颈
  7. 最佳实践

    • 在开发环境显示详细错误信息
    • 在生产环境隐藏错误并记录日志
    • 实现友好的错误页面
    • 建立错误通知机制

良好的错误处理和调试能力是PHP开发者的必备技能。通过系统地学习和实践这些技巧,你可以:

  • 快速定位问题:准确找到错误发生的位置和原因
  • 提高代码质量:通过调试发现和修复潜在问题
  • 优化应用性能:识别和解决性能瓶颈
  • 增强用户体验:提供友好的错误处理和恢复机制
  • 简化维护工作:通过日志和监控快速诊断问题

记住,调试不仅仅是修复错误,更是理解代码行为、优化性能和提高代码质量的重要手段。

希望这篇文章能帮助你掌握PHP错误处理与调试的核心技能!

本站由 提供部署服务