0%

PHP异常处理

pcntl_fork的作用是在当前进程当前位置产生分支(子进程)。译注:fork是创建了一个子进程,父进程和子进程都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程号,而子进程得到的是0。

一、概念

  1. 异常与错误的区别(PHP5/PHP7+重大变化)
  • PHP5:Exception 是异常,属于可捕获;程序致命错误(Fatal Error)无法被 try-catch 捕获,直接终止脚本。
  • PHP7+:把大部分致命错误改为Error 类,Error 和 Exception 都实现 Throwable 接口。

Throwable 是顶层接口,有两个核心实现:

Exception:业务异常,开发者主动抛出
Error:PHP 底层错误(语法错误、调用不存在函数、内存溢出等)

简单区分:

  • Exception:业务逻辑问题,可以主动 throw
  • Error:引擎级错误,一般由 PHP 底层抛出,代码里不建议手动 new Error
  1. 核心关键字
  • try:包裹可能抛出异常的代码块
  • catch:捕获指定类型异常,可以多 catch
  • throw:主动抛出异常实例
  • finally:无论是否发生异常,一定会执行(PHP5.5+),常用于资源释放:关闭文件、数据库连接等
  1. 异常传播机制

当代码 throw 抛出异常后,会终止当前代码执行,向上寻找匹配的 catch;

如果一直没有捕获,触发全局未捕获异常处理函数,最后脚本终止输出报错。

  1. 全局异常与错误处理函数
  • set_exception_handler():注册未捕获异常回调(只处理Throwable)
  • set_error_handler():接管传统错误(warning/notice 等,不会接管Fatal Error,PHP7 fatal归入Error)
  • register_shutdown_function():脚本终止前执行,可捕获最后致命错误

注意:set_error_handler 捕获的错误,不会自动变成 Throwable,需要手动转抛。

  1. 异常栈追踪方法
  • getTrace():返回数组格式调用栈信息,每一项包含函数、文件、行号、参数
  • getTraceAsString():返回字符串格式完整调用栈,日志记录最常用,一行行展示函数调用链路

作用:查看异常是经过哪些函数层层调用才触发的,定位复杂业务链路问题。

二、实战

  1. 基础 try catch finally
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
try {
$num = 0;
if ($num === 0) {
throw new Exception("除数不能为0", 400);
}
$res = 10 / $num;
} catch (Exception $e) {
// 获取异常基础信息
echo "异常信息:" . $e->getMessage() . PHP_EOL;
echo "异常码:" . $e->getCode() . PHP_EOL;
echo "文件:" . $e->getFile() . PHP_EOL;
echo "行号:" . $e->getLine() . PHP_EOL;
} finally {
echo "这里无论是否异常都会执行,释放资源\n";
}
  1. 多 catch,区分 Exception / Error(PHP7+)
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
try {
// 调用不存在函数,会抛出 Error
test_func();
} catch (Error $e) {
echo "引擎错误:" . $e->getMessage();
} catch (Exception $e) {
echo "业务异常:" . $e->getMessage();
}

try {
$a='str';
$a++;
// 调用不存在函数,会抛出 Error
test_func();
} catch (Error $e) {
echo "引擎错误:" . $e->getMessage() . PHP_EOL;
// 打印字符串形式调用栈
echo "异常堆栈信息1:" . $e->getTraceAsString() . PHP_EOL;
} catch (Exception $e) {
echo "业务异常:" . $e->getMessage() . PHP_EOL;
echo "异常堆栈信息2:" . $e->getTraceAsString() . PHP_EOL;
}



try {
$a='str';
$a++;
test_func();
} catch (Throwable $e) {
echo "错误/异常信息:" . $e->getMessage() . PHP_EOL;
echo "文件:{$e->getFile()} 行号:{$e->getLine()}" . PHP_EOL;
echo "堆栈:" . $e->getTraceAsString() . PHP_EOL;
}



function func1()
{
func2();
}
function func2()
{
test_func();
}

try {
$a='str';
$a++;
func1();
} catch (Throwable $e) {
echo "【捕获异常】" . $e->getMessage() . PHP_EOL;
echo "文件: {$e->getFile()}, 行号: {$e->getLine()}" . PHP_EOL;
echo "堆栈追踪:" . $e->getTraceAsString();
}
  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
<?php
class BusinessException extends Exception
{

}

class UserNotFoundException extends BusinessException
{

}

function checkUser($userId)
{
if ($userId <= 0) {
throw new UserNotFoundException("用户不存在", 10001);
}
}

function login($uid)
{
checkUser($uid);
}

try {
$userId = 0;
login($userId);
} catch (UserNotFoundException $e) {
echo "【用户异常】" . $e->getMessage() . PHP_EOL;
echo "=====异常调用栈 getTraceAsString=====" . PHP_EOL;
echo $e->getTraceAsString() . PHP_EOL;
} catch (BusinessException $e) {
echo "【通用业务异常】" . $e->getMessage();
}

  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
<?php
// 全局未捕获异常处理
set_exception_handler(function (Throwable $e) {
$log = [
'code' => $e->getCode(),
'msg' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
// 实际项目写入日志文件,不要直接返回trace给前端
echo json_encode([
'code' => $e->getCode(),
'msg' => $e->getMessage()
]);
// file_put_contents('./exception.log', json_encode($log, JSON_UNESCAPED_UNICODE) . PHP_EOL, FILE_APPEND);
});

function testA()
{
testB();
}
function testB()
{
throw new Exception("全局异常测试", 500);
}
testA();

  1. 错误处理器,将传统错误转为异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
// 将notice/warning转为异常
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
return;
}
throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);
});

try {
$a = $b; // $b未定义,触发notice,被转为异常
} catch (ErrorException $e) {
echo "捕获到错误转异常:" . $e->getMessage() . PHP_EOL;
echo "异常栈:" . $e->getTraceAsString();
}

三. 开发注意事项

  1. finally 里如果有 return,会覆盖 try/catch 中的返回值,谨慎使用;
  2. try 不要包裹过大范围代码,精准包裹可能抛异常的片段;
  3. 生产环境不要直接对外输出 getFile/getLine/getTrace,泄露源码路径;记录到日志即可;
  4. Throwable 不能被 try-catch(Exception $e) 完整捕获(PHP7 Error 不属于 Exception),需要单独捕获或捕获 Throwable;
  5. pcntl_fork 子进程中的异常:父子进程异常隔离,父进程 try 无法捕获子进程内部抛出异常,子进程需要自己做异常捕获。

四、参考

  1. 参考一
  2. 参考二