捕获包装致命 PHP 错误的 ErrorException。

5
在我自己编写的PHP MVC框架中,我写了一个小型错误处理程序,它可以将PHP错误包装成异常并抛出。
class ErrorController extends ControllerAbstract {

    ...

    public static function createErrorException($number, $message = NULL, $file = NULL, $line = NULL, array $context = array()) {
        throw new ErrorException($message, $number, 0, $file, $line);
    }
}

使用 set_error_handler() 注册错误处理程序。这个方法很好用,但是遇到致命错误时(不是故意的),无法捕获抛出的 ErrorException 异常。

比如尝试包含一个不存在的文件:

    try {
        require 'Controller/NonExistentController.php';
    } catch (ErrorException $exc) {
        echo $exc->getTraceAsString(); // code never reaches this block
    }

我的自定义错误处理程序被调用并抛出了异常,但代码从未到达“catch”块。相反,PHP会生成HTML(不好!):


警告:Uncaught exception 'ErrorException' with message 'require(Controller/NonExistentController.php): failed to open stream: ...

随后是:

致命错误:Program::main():打开所需的“Controller/NonExistentController.php”失败(include_path ='.:')...

我不想尝试从致命错误中恢复,但我希望我的代码能够优雅地退出。在这种情况下,这意味着发送一个XML或JSON响应,指示内部错误,因为这是一个REST应用程序,我的客户端期望如此。 HTML响应很可能也会破坏客户端应用程序。
我应该以不同的方式处理吗?

1
哈哈,PHP错误处理总是让我咯咯笑。"@,关于不存在的文件的警告将不会被打印出来。 但如果在php.ini中设置了scream.enabled,它将被打印出来。 或者如果使用ini_set手动设置了scream.enabled,它也会被打印出来。 但如果正确的error_reporting级别没有设置,它就不会被打印出来。 如果被打印出来,它确切的输出位置取决于display_errors,同样是在php.ini或ini_set中设置。是的... - Alec Teal
1
哦,还有我最喜欢的“可捕获致命错误”。 - Alec Teal
我承认在C#中异常处理非常容易,但在PHP中肯定也不会那么难吧? - Steven Liekens
PHPжңүеҫҲеӨҡй—®йўҳпјҢдҫӢеҰӮ==иҝҗз®—з¬ҰжҳҜй”ҷиҜҜзҡ„пјҢstrposиҝ”еӣһ0еҸҜиғҪдјҡиў«и§Ҷдёәfalse...иҜ·йҳ…иҜ»http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/гҖӮжҲ‘зңҹзҡ„дёҚи®ӨдёәиҝҷдёӘй—®йўҳеҸҜд»Ҙи§ЈеҶіпјҢиҷҪ然еҠӘеҠӣдәҶпјҢдҪҶ...еҖјеҫ—е°қиҜ•еҗ—пјҹ - Alec Teal
我对PHP随意生成HTML输出并不满意。虽然我还没有研究如何配置php.ini文件,但我认为世界已经摆脱了ini文件。 - Steven Liekens
除了使用@运算符来隐藏这些烦人的消息之外,没有其他方法,但正如@Alec Teal所说,如果scream.enabled被设置,你无能为力,但通常情况下它是禁用的。 - Henrique Barcelos
2个回答

7
请查看php.net关于require的文档:

requireinclude几乎相同,但在失败时它还会产生致命的E_COMPILE_ERROR级别错误。换句话说,它将停止脚本,而include只发出警告(E_WARNING),这允许脚本继续运行。

在您的情况下,您可以使用register_shutdown_function来处理致命错误,需要PHP 5.2+:
function customFatalHandler() {
  $error = error_get_last();

  if( $error === NULL) {
      return;
  }

  $type   = $error["type"];
  $file = $error["file"];
  $line = $error["line"];
  $message  = $error["message"];

  echo "Error `$type` in file `$file` on line $line with message `$message`"; 
}

register_shutdown_function("customFatalHandler");

还有这些也可能对你有帮助

  1. error_get_last() 和自定义错误处理程序
  2. 为什么PHP无法捕获“类未找到”错误?

直到它打在我的脸上,我才明白requireinclude之间的区别。感谢您澄清了这一点! - Steven Liekens
我已成功实现了您的解决方案,但它没有抑制HTML警告。这是怎么回事? - Steven Liekens
答案是https://dev59.com/HmzXa4cB1Zd3GeqPRkX3 - Nick Bondarenko
你是对的。在 require 语句前加上 @ 运算符也可以工作。我仍然想知道我应该如何了解所有这些。 - Steven Liekens
啊!这真的让我很困扰。似乎一行代码可以根据错误报告设置生成多个错误(例如1个E_WARNING和1个E_ERROR)。你觉得这样对吗? - Steven Liekens

0

很抱歉给出这样的负面答案,但你(非常可能)做不到。

PHP被设计成尽可能长时间地运行,出现了一些奇怪的问题,比如你有一个错别字?默默地将该变量初始化为null,你在某个东西后面加上[],如果操作符没有意义,则评估为null!

这在错误处理方面更加混乱,有大约4个系统在起作用,它们不能协同工作。PHP通过“猴子补丁”进行一些错误处理,如果您尝试通过抛出异常在错误处理程序中处理某些内容,它直到处理程序退出才会“取消补丁”,这会导致问题。

使用关闭处理程序时,从那里抛出异常可能会导致巨大的问题!我以前就遇到过这种情况,我不想谈论它:P

在PHP中,“资源”文件(也称为文件资源)是否在关闭时关闭是实现定义的,它是为帮助插件在Web服务器上整理而创建的。(因此您可以拥有它们的列表)


就在我以为我开始掌握它的时候,这真是令人沮丧。:( - Steven Liekens
@StevenLiekens 我讨厌 PHP,我讨厌它的“数组”既不是列表也不是关联容器,我讨厌错误处理,我讨厌那些并没有真正在更大的图景中发挥作用的附加功能。PHP 是一种特殊的语言,因为它从来都不是自然而然的,你总是需要搜索,是 str2upper 还是 str_to_upper?不!它是 strtoupper,等等 str_replace,什么鬼?如果你可以使用 Python,就用 Python 吧。它是一种有意义、一致且经过深思熟虑的语言,比 PHP 容易得多。 - Alec Teal
好的,我正在进行交叉编译,所以我可能会花费更多的时间来使Python工作而不是避开PHP的一些愚蠢之处。但我会考虑的。 - Steven Liekens
交叉编译...php?不过Python更容易上手,更容易扩展...真的值得一试。 - Alec Teal
我指的是实际的PHP解释器,而不是我的脚本。我正在为不同的CPU架构(确切地说是MIPS)编译,其中不存在预编译的二进制文件。我已经让PHP工作了,但Python可能完全是另一回事。 - Steven Liekens
@StevenLiekens 不是的,我一开始以为你是指编译PHP脚本!(PHP确实有那个C++东西...)无论如何,不,Python要简单得多,我能够轻松地将其编译到我的图形计算器(Casio FX 98600GII,没有彩色屏幕或超过64kb的内存:P)上,Python被设计成小巧便携,这就是为什么它是每个Linux和BSD发行版的核心部分。 - Alec Teal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接