我需要捕捉一些警告从一些php本机函数抛出,然后处理它们。

具体地说:

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

当DNS查询失败时,它抛出一个警告。

Try /catch不起作用,因为警告不是异常。

我现在有两个选择:

set_error_handler似乎是多余的,因为我必须使用它来过滤页面中的每个警告(这是真的吗?); 调整错误报告/显示,这样这些警告就不会显示在屏幕上,然后检查返回值;如果为假,则没有找到主机名的记录。

这里的最佳实践是什么?


当前回答

设置和恢复错误处理程序

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

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

您可以在此基础上编写一个可重用的错误处理程序,为您记录错误。

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

将错误转化为异常

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

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    // 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()。

抑制警告

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

其他回答

您可能应该尝试完全摆脱警告,但如果这是不可能的,您可以在调用之前使用@(即@dns_get_record(…)),然后使用您可以获得的任何信息来确定警告是否发生。

结合file_get_contents()调用外部url的这几行代码,帮助我更好地处理像“未能打开流:连接超时”这样的警告:

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();

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

public function myContentGetter($sUrl)
{
  ... code above ...
  return $iResult;
}

从PHP8开始,您可以执行以下操作,而不是设置错误处理程序来捕获错误和警告。我相信PHP 7。你可以捕捉一些错误。

try {
    call_user_func('sprintf', array_merge([$string], $args));
} catch (Throwable $e) {
    $logger->info('mesage...');
}

如果以这种方式隔离,您通常应该在可以传递或访问记录器的地方,因为它可以混淆编码器错误,例如将错误输入的参数传递给方法,并掩盖各种其他问题。

https://php.watch/versions/8.0/internal-function-exceptions

不确定是否注意到(可能没有),但您可以通过更多地考虑您想要做的事情来解决像这样的例子。

构建器模式和选项模式都为此提供了解决方案,在调用之前(可以是私有函数,也可以是有效性检查之后),您可以抛出一个真正的自定义异常,该异常仅可归因于您的代码。这将使内置函数使用起来非常安全。

另一个很好的做法是使用debug_backtrace和DEBUG_BACKTRACE_IGNORE_ARGS,或者在Throwable对象上使用getTrace或getTraceAsString方法,这样可以保留一些上下文。

设置和恢复错误处理程序

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

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

您可以在此基础上编写一个可重用的错误处理程序,为您记录错误。

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

将错误转化为异常

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

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    // 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()。

抑制警告

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

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