全局 exception handler 的黑科技用法
昨天在写 overtrue/wechat 3.0 的时候,考虑到用户 debug 的问题。期望把日志包括产生的异常日志都记到用户配置的日志文件里。
因为代码在不同的组件,不可能用 try...catch
。我打算使用 set_exception_handler 注册一个全局异常处理器来做这事儿,但是,我这个只是一个开源组件,可能会被用户用到各种各样的环境中,所以,不能破坏原有框架或者用户自己定义的异常处理器,因为 set_exception_handler
会覆盖前面设置的,所以问题就卡住了。
然后我找到了 restore_exception_handler,以为找到了救命稻草,于是我把代码改成如下:
<?php
class MyException extends Exception {}
set_exception_handler(function(Exception $e){
echo "Old handler:".$e->getMessage();
});
set_exception_handler(function(Exception $e) {
if ($e instanceof MyException) {
echo "New handler:".$e->getMessage();
return;
}
restore_exception_handler(); // 还原之前的设定然后下面再抛出
throw $e;
});
// throw new MyException("Exception two", 1);
throw new Exception("Exception two", 1);
然后就报错了...
PHP Fatal error: Cannot destroy active lambda function in /Users/overtrue/www/foo.php on line 15
于是 google, stackoverflow... 都无解。
后来又仔细研究了文档,终于,我发现了:
Returns the name of the previously defined exception handler, or NULL on error. If no previous handler was defined, NULL is also returned.
于是此问题得以圆满解决,虽然恢复原有的 handler 是不可能了,但是达到同样的效果就 OK 了。
<?php
class MyException extends Exception {}
set_exception_handler(function(Exception $e){
echo "Old handler:".$e->getMessage();
});
$lastHandler = set_exception_handler(function(Exception $e) use (&$lastHandler) {
if ($e instanceof MyException) {
echo "New handler:".$e->getMessage();
return;
}
if (is_callable($lastHandler)) {
return call_user_func_array($lastHandler, [$e]);
}
});
// throw new MyException("Exception two", 1);
throw new Exception("Exception two", 1);
利用 PHP 的引用,把 $lastHandler
引用到闭包里,这样毕竟 set_exception_handler
会比闭包先运行,所以就会把前一个 handler
拿到了,然后异常的时候,发现不是我的 MyException
就直接调用原来的 handler 来处理就好了。
然后我去回答了我之前搜索到没有答案的两个问题:
希望有此想法的同学能得到帮助。 😄