关于php:我可以尝试/捕获警告吗?

Can I try/catch a warning?

我需要捕获一些来自PHP本机函数的警告,然后处理它们。

明确地:

1
array dns_get_record  ( string $hostname  [, int $type= DNS_ANY  [, array &$authns  [, array &$addtl  ]]] )

当DNS查询失败时,它会发出警告。

try/catch不起作用,因为警告不是例外。

我现在有两个选择:

  • set_error_handler看起来像是杀伤力过大,因为我必须使用它来过滤页面中的每个警告(这是真的吗?);

  • 调整错误报告/显示,使这些警告不会回显到屏幕上,然后检查返回值;如果是false,则找不到主机名的记录。

  • 这里的最佳实践是什么?


    设置和还原错误处理程序

    一种可能是在调用之前设置自己的错误处理程序,然后使用restore_error_handler()恢复以前的错误处理程序。

    1
    2
    3
    set_error_handler(function() { /* ignore errors */ });
    dns_get_record();
    restore_error_handler();

    您可以基于这个想法,编写一个可重用的错误处理程序,为您记录错误。

    1
    2
    3
    set_error_handler([$logger, 'onSilencedError']);
    dns_get_record();
    restore_error_handler();

    将错误转化为异常

    可以使用set_error_handler()ErrorException类将所有PHP错误转换为异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
        // error was suppressed with the @-operator
        if (0 === error_reporting()) {
            return false;
        }

        throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    });

    try {
        dns_get_record();
    } catch (ErrorException $e) {
        // ...
    }

    在使用自己的错误处理程序时需要注意的一点是,它将绕过error_reporting设置,并将所有错误(通知、警告等)传递给错误处理程序。您可以在set_error_handler()上设置第二个参数来定义要接收的错误类型,或者在错误处理程序中使用... = error_reporting()访问当前设置。

    取消警告

    另一种可能是使用@operator抑制调用,然后检查dns_get_record()的返回值。但我建议不要这样做,因为错误/警告会被触发处理,而不是被抑制。


    实际可行的解决方案是使用E_WARNING参数设置简单的错误处理程序,如:

    1
    2
    3
    4
    5
    6
    7
    set_error_handler("warning_handler", E_WARNING);
    dns_get_record(...)
    restore_error_handler();

    function warning_handler($errno, $errstr) {
    // do something
    }


    当心@操作员,它会抑制警告,同时也会抑制致命错误。我花了很多时间在一个系统中调试一个问题,其中有人编写了@mysql_query( '...' ),问题是mysql支持没有加载到php中,它抛出了一个无声的致命错误。对于那些属于PHP核心的东西是安全的,但是请小心使用。

    1
    2
    3
    4
    bob@mypc:~$ php -a
    Interactive shell

    php > echo @something(); // this will just silently die...

    没有进一步的输出-祝您调试顺利!

    1
    2
    3
    4
    5
    6
    7
    8
    bob@mypc:~$ php -a
    Interactive shell

    php > echo something(); // lets try it again but don't suppress the error
    PHP Fatal error:  Call to undefined function something() in php shell code on line 1
    PHP Stack trace:
    PHP   1. {main}() php shell code:0
    bob@mypc:~$

    这一次我们可以看到失败的原因。


    我想尝试/捕捉一个警告,但同时保留通常的警告/错误日志(例如在/var/log/apache2/error.log中);为此,处理程序必须返回FALSE。但是,由于"throw new…"语句基本上会中断执行,因此必须执行"wrap-in-function"技巧,还将在以下内容中讨论:

    有静态方法可以在PHP中引发异常吗

    或者,简而言之:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      function throwErrorException($errstr = null,$code = null, $errno = null, $errfile = null, $errline = null) {
        throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
      }
      function warning_handler($errno, $errstr, $errfile, $errline, array $errcontext) {
        return false && throwErrorException($errstr, 0, $errno, $errfile, $errline);
        # error_log("AAA"); # will never run after throw
       /* Do execute PHP internal error handler */
        # return false; # will never run after throw
     }
      ...
      set_error_handler('warning_handler', E_WARNING);
      ...
      try {
        mkdir($path, 0777, true);
      } catch (Exception $e) {
        echo $e->getMessage();
        // ...
      }

    编辑:经过仔细检查,发现它不起作用:"return false && throwErrorException ...基本上不会抛出异常,只需登录错误日志;删除"false &&部分,如"return throwErrorException ...部分",会使异常抛出起作用,但不会登录错误日志……不过,我还是会把这个贴出来,因为我在别处没有看到这种行为的记录。


    您可能应该尝试完全消除警告,但如果不可能,您可以使用@(即@dns_get_record(…))预先结束呼叫,然后使用您可以得到的任何信息来确定警告是否发生。


    将这些代码行与对外部URL的file_get_contents()调用结合起来,可以帮助我处理诸如"未能打开流:连接超时"之类的警告,效果更好:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    set_error_handler(function ($err_severity, $err_msg, $err_file, $err_line, array $err_context)
    {
        throw new ErrorException( $err_msg, 0, $err_severity, $err_file, $err_line );
    }, E_WARNING);
    try {
        $iResult = file_get_contents($sUrl);
    } catch (Exception $e) {
        $this->sErrorMsg = $e->getMessage();
    }
    restore_error_handler();

    这个解决方案也适用于对象上下文。您可以在函数中使用它:

    1
    2
    3
    4
    5
    public function myContentGetter($sUrl)
    {
      ... code above ...
      return $iResult;
    }


    正常情况下,除非这是唯一的解决方案,否则不应使用@。在这种特定情况下,应该首先使用函数dns_check_record来了解该记录是否存在。


    如果dns_get_record()失败,则返回FALSE,可以用@抑制警告,然后检查返回值。


    尝试检查它是否返回一些布尔值,然后您可以简单地将其作为一个条件。我在oci_execute(…)中遇到了这个问题,它使用我的唯一键返回了一些冲突。

    1
    2
    3
    4
    5
    ex.
    oci_parse($res,"[oracle pl/sql]");
    if(oci_execute){
    ...do something
    }

    我只建议在直接操作时使用@来禁止警告(例如$prop=@($high/($width-$depth));跳过零警告的除法)。不过,在大多数情况下,最好处理。