不被抛出的异常不是异常

这个标题看起来难免有些奇怪,不过在PHP中,确实存在这样的问题。当你只是简单的将可能出现异常的代码用try catch包起来,妄图接住Exception的时候,往往得不到想要的效果。接下来,我们通过几段代码,来看一下问题的本质。

不能被捕获的异常

try {
    $file = fopen('a.txt', 'r');
    fread($file, 10);
    fclose($file);
} catch (Exception $e) {
    var_dump($e);		
}

当a.txt不存在时,以上代码不会抛出错误并且走到catch里面,而是如下3个warning:

PHP Warning:  fopen(a.txt): failed to open stream: No such file or directory in /Users/roychen/Dev/php-at/test.php
PHP Warning:  fread() expects parameter 1 to be resource, boolean given in /Users/roychen/Dev/php-at/test.php
PHP Warning:  fclose() expects parameter 1 to be resource, boolean given in /Users/roychen/Dev/php-at/test.php

正确的抛出姿势

try {
    $file = @fopen('a.txt', 'r');
    if (!$file) {
        throw new Exception('Can not open file');
    }
    fread($file, 10);
    fclose($file);
} catch (Exception $e) {
    var_dump($e);		
}
原理:用“@”抑制住可能发生的异常(这里指文件不存在),然后通过函数的返回(如果有异常则返回false)判断是否需要抛出异常。如此一来,异常就能被catch捕获了。

@ 符号简单介绍

@ 符号是PHP中比较特别(至少Javascript中没有)的一个符号,我认为是一个语法糖。主要作用就是使得@符号后面的语句不会抛出异常,反而使用Boolean返回值表示执行结果。常用情况:

  1. 打日志的时候
  2. 次要逻辑,不影响主流程。比方,用户注册完账户,发送感谢邮件。

最佳实践

上面我们讲了,如何在代码中跑出异常、在什么情况下使用@符号,但如果一直需要在业务层面解决异常的处理问题,始终难以避免异常的发生。接下来,带来一种方法,我认为他可能是php关于异常处理的最佳实践:set_error_handler。

set_error_handler

关于此函数的官方解释,请看这里。接下来,我们在全局设置一个error_handler。

function my_error_handler($errno, $errstr, $errfile, $errline) {	
  // 写日志
  // 发送告警
}

set_error_handler('my_error_handler');

这是,再来看看上面一段文件写入的代码应该怎么写了?

function my_error_handler($errno, $errstr, $errfile, $errline) {	
  // 写日志
  // 发送告警
}

set_error_handler('my_error_handler');
    
$file = fopen('a.txt', 'r');
fread($file, 10);
fclose($file);

#####my_error_handler会统一处理告警和错误日志记录的工作,当然,你如果希望在逻辑种特殊处理,那么也可以通过判断$file来自行添加处理逻辑。