从 Division by zero 到 set_error_handler
最近做了个简单的表格应用,其中有个功能是支持公式运算,如下图,毛利率这列是其他两列相除算出来的:

Division by zero
做完,客户还没用一天,就碰到问题了,因为有时候公式中的被除数是 0,这时候 log 中一堆 「Division by zero
」的 Warning,影响查看其他的 log 的查看。
刚开始我想到的办法是在填充公式变量的时候,判断一下被除数是零就抛出「除零错误」,不去真正计算就好了,确实可以解决问题。嘻嘻,我还是挺聪明的。😎
但是很快又碰到新的问题了,因为有这样的公式:$a / ($b + $c)
,然后 ($b + $c)
的结果为 0。😂
如果继续按照原来的做法,我要根据运算的优先级,来判断被除数是 0,如果公式一复杂,那么什么时候是个头啊。🤦🤦♂️🤦♀️🤕
Warning 还是异常
先搜索了一圈 Division by zero
,然后在 PHP 文档,看到 PHP 就已经有一个预定义的异常 DivisionByZeroError
。
但是为啥我的代码能够执行成功,只是在 log 中出现一堆 「Division by zero
」的 Warning,那就继续看文档:
在 PHP 7 中使用算术运算符 /
不会抛出异常,而在 PHP 8 中会抛出异常,可惜客户的系统还在使用 PHP 7.4。
直接问 DeepSeek 我该怎么办?它给了我四个方法:
1. 手动检查并抛出异常
我这个又不是简单的被除数,真的是。🙄
2. 使用 set_error_handler
捕获警告
貌似这个可以。😘
3. 封装除法操作类
我是算术公式,这个不合适。🤪
4. 升级 PHP 8
我能升级就不会问你了。🤬
看来使用方案二了!
使用 set_error_handler
捕获警告
就是通过 set_error_handler
函数将警告转换成异常抛出。
set_error_handler(function($no, $str){
if(str_contains($str , 'Division by zero')){
throw new DivisionByZeroError($str);
}
throw new ErrorException($str , $no);
return true;
});
如果警告信息的字符串里面有 Division by zero
,就抛出 DivisionByZeroError
异常,其他情况抛出 ErrorException
。
然后原来的执行表达式运算的代码改成 try - catch
结构:
try{
// 原来执行运算表达式代码
}catch(DivisionByZeroError $e){
return $if_error ?? '!除零错误';
}catch(throwable $e){
return $if_error ?? '!计算错误';
}
完美解决!🍾🥂🎆🎇🎊
还没开心 5 分钟,很快就来了但是。😞😮💨
如果这样自定义错误处理程序,那么之后的代码的警告都被他接管了,然后直接抛出异常。这 🙄🫤
继续查 PHP 文档,问 DeepSeek,原来 set_error_handler
有返回值的,如果之前有定义的错误处理程序,则返回之前定义的错误处理程序,如果没有定义或者是内置的错误处理程序,则返回 null。
这样的话,那就好处理,给 try - catch
结构加上 finally
,最终的代码如下:
$handler = set_error_handler(function($no, $str){
if(str_contains($str , 'Division by zero')){
throw new DivisionByZeroError($str);
}
throw new ErrorException($str , $no);
return true;
});
try{
// 原来执行运算表达式代码
}catch(DivisionByZeroError $e){
return $if_error ?? '!除零错误';
}catch(throwable $e){
return $if_error ?? '!计算错误';
}finally{
if($handler){ // 之前有定义的错误处理程序
set_error_handler($handler);
}else{ // 恢复到内置的错误处理程序
restore_error_handler();
}
}
搞定,收工,记录一下!📝 🐂🍺