pcntl_fork的作用是在当前进程当前位置产生分支(子进程)。译注:fork是创建了一个子进程,父进程和子进程都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程号,而子进程得到的是0。
一、概念
- 异常与错误的区别(PHP5/PHP7+重大变化)
- PHP5:
Exception 是异常,属于可捕获;程序致命错误(Fatal Error)无法被 try-catch 捕获,直接终止脚本。
- PHP7+:把大部分致命错误改为Error 类,
Error 和 Exception 都实现 Throwable 接口。
Throwable 是顶层接口,有两个核心实现:
Exception:业务异常,开发者主动抛出
Error:PHP 底层错误(语法错误、调用不存在函数、内存溢出等)
简单区分:
Exception:业务逻辑问题,可以主动 throw
Error:引擎级错误,一般由 PHP 底层抛出,代码里不建议手动 new Error
- 核心关键字
try:包裹可能抛出异常的代码块
catch:捕获指定类型异常,可以多 catch
throw:主动抛出异常实例
finally:无论是否发生异常,一定会执行(PHP5.5+),常用于资源释放:关闭文件、数据库连接等
- 异常传播机制
当代码 throw 抛出异常后,会终止当前代码执行,向上寻找匹配的 catch;
如果一直没有捕获,触发全局未捕获异常处理函数,最后脚本终止输出报错。
- 全局异常与错误处理函数
set_exception_handler():注册未捕获异常回调(只处理Throwable)
set_error_handler():接管传统错误(warning/notice 等,不会接管Fatal Error,PHP7 fatal归入Error)
register_shutdown_function():脚本终止前执行,可捕获最后致命错误
注意:set_error_handler 捕获的错误,不会自动变成 Throwable,需要手动转抛。
- 异常栈追踪方法
getTrace():返回数组格式调用栈信息,每一项包含函数、文件、行号、参数
getTraceAsString():返回字符串格式完整调用栈,日志记录最常用,一行行展示函数调用链路
作用:查看异常是经过哪些函数层层调用才触发的,定位复杂业务链路问题。
二、实战
- 基础 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"; }
|
- 多 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 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 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 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(); }
|
三. 开发注意事项
finally 里如果有 return,会覆盖 try/catch 中的返回值,谨慎使用;
try 不要包裹过大范围代码,精准包裹可能抛异常的片段;
- 生产环境不要直接对外输出
getFile/getLine/getTrace,泄露源码路径;记录到日志即可;
Throwable 不能被 try-catch(Exception $e) 完整捕获(PHP7 Error 不属于 Exception),需要单独捕获或捕获 Throwable;
- pcntl_fork 子进程中的异常:父子进程异常隔离,父进程 try 无法捕获子进程内部抛出异常,子进程需要自己做异常捕获。
四、参考
- 参考一
- 参考二