我可以使用set_error_handler()
来捕获大多数PHP错误,但它无法处理致命错误(E_ERROR
),例如调用不存在的函数。是否有另一种方法来捕获这些错误?
我正尝试在所有错误时调用mail()
,并且正在运行PHP 5.2.3。
我可以使用set_error_handler()
来捕获大多数PHP错误,但它无法处理致命错误(E_ERROR
),例如调用不存在的函数。是否有另一种方法来捕获这些错误?
我正尝试在所有错误时调用mail()
,并且正在运行PHP 5.2.3。
这里有一个很好的技巧,可以获取当前的错误处理程序方法 =)
<?php
register_shutdown_function('__fatalHandler');
function __fatalHandler()
{
$error = error_get_last();
// Check if it's a core/fatal error. Otherwise, it's a normal shutdown
if($error !== NULL && $error['type'] === E_ERROR) {
// It is a bit hackish, but the set_exception_handler
// will return the old handler
function fakeHandler() { }
$handler = set_exception_handler('fakeHandler');
restore_exception_handler();
if($handler !== null) {
call_user_func(
$handler,
new ErrorException(
$error['message'],
$error['type'],
0,
$error['file'],
$error['line']));
}
exit;
}
}
?>
<?php
ini_set('display_errors', false);
?>
PHP停止显示错误。否则,错误文本将在您的错误处理程序之前发送到客户端。
/**
* ErrorHandler that can be used to catch internal PHP errors
* and convert to an ErrorException instance.
*/
abstract class ErrorHandler
{
/**
* Active stack
*
* @var array
*/
protected static $stack = array();
/**
* Check if this error handler is active
*
* @return bool
*/
public static function started()
{
return (bool) static::getNestedLevel();
}
/**
* Get the current nested level
*
* @return int
*/
public static function getNestedLevel()
{
return count(static::$stack);
}
/**
* Starting the error handler
*
* @param int $errorLevel
*/
public static function start($errorLevel = \E_WARNING)
{
if (!static::$stack) {
set_error_handler(array(get_called_class(), 'addError'), $errorLevel);
}
static::$stack[] = null;
}
/**
* Stopping the error handler
*
* @param bool $throw Throw the ErrorException if any
* @return null|ErrorException
* @throws ErrorException If an error has been catched and $throw is true
*/
public static function stop($throw = false)
{
$errorException = null;
if (static::$stack) {
$errorException = array_pop(static::$stack);
if (!static::$stack) {
restore_error_handler();
}
if ($errorException && $throw) {
throw $errorException;
}
}
return $errorException;
}
/**
* Stop all active handler
*
* @return void
*/
public static function clean()
{
if (static::$stack) {
restore_error_handler();
}
static::$stack = array();
}
/**
* Add an error to the stack
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return void
*/
public static function addError($errno, $errstr = '', $errfile = '', $errline = 0)
{
$stack = & static::$stack[count(static::$stack) - 1];
$stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack);
}
}
如果需要,此类允许您启动特定的ErrorHandler
,然后您还可以停止处理程序。
例如,可以像这样使用此类:
ErrorHandler::start(E_WARNING);
$return = call_function_raises_E_WARNING();
if ($innerException = ErrorHandler::stop()) {
throw new Exception('Special Exception Text', 0, $innerException);
}
// or
ErrorHandler::stop(true); // directly throws an Exception;
完整类代码链接:
https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php
它还可以使用register_shutdown_function
函数处理致命错误。根据此类,致命错误包括以下错误类型:array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR)
。
class ErrorHandler
{
// [...]
public function registerExceptionHandler($level = null, $callPrevious = true)
{
$prev = set_exception_handler(array($this, 'handleException'));
$this->uncaughtExceptionLevel = $level;
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
}
}
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1)
{
$prev = set_error_handler(array($this, 'handleError'), $errorTypes);
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
if ($callPrevious) {
$this->previousErrorHandler = $prev ?: true;
}
}
public function registerFatalHandler($level = null, $reservedMemorySize = 20)
{
register_shutdown_function(array($this, 'handleFatalError'));
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
$this->fatalLevel = $level;
}
// [...]
}
PHP存在可捕获的致命错误,它们被定义为E_RECOVERABLE_ERROR。PHP手册描述E_RECOVERABLE_ERROR如下:
可捕获的致命错误。它表示可能发生了危险的错误,但未使引擎处于不稳定的状态。如果用户没有定义句柄来处理该错误(参见set_error_handler()),则应用程序将停止,就像一个E_ERROR一样。
您可以使用set_error_handler()并检查是否存在E_RECOVERABLE_ERROR来“捕获”这些“致命”错误。当捕获到此错误时,我认为将其抛出异常是很有用的,然后您可以使用try/catch。
以下问题和回答提供了一个有用的示例:如何在PHP类型提示中捕获“可捕获的致命错误”?
然而,E_ERROR错误可以被处理,但无法从中恢复,因为引擎处于不稳定状态。
由于这里的大多数答案都冗长无用,以下是我非丑陋的版本,是排名第一的答案:
function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) {
//Do stuff: mail, log, etc
}
function fatalHandler() {
$error = error_get_last();
if($error) errorHandler($error["type"], $error["message"], $error["file"], $error["line"]);
}
set_error_handler("errorHandler")
register_shutdown_function("fatalHandler");
有些情况下,即使是致命错误也应该被捕获(您可能需要在优雅地退出之前进行一些清理,而不仅仅是停止运行)。
我在我的 CodeIgniter 应用程序中实现了一个 pre_system 钩子,以便我可以通过电子邮件获得我的致命错误,并且这帮助我找到了未报告的错误(或者在修复后才报告,因为我已经知道它们 :))。
Sendemail 检查错误是否已经报告,以便不会多次发送已知错误的垃圾邮件。
class PHPFatalError {
public function setHandler() {
register_shutdown_function('handleShutdown');
}
}
function handleShutdown() {
if (($error = error_get_last())) {
ob_start();
echo "<pre>";
var_dump($error);
echo "</pre>";
$message = ob_get_clean();
sendEmail($message);
ob_start();
echo '{"status":"error","message":"Internal application error!"}';
ob_flush();
exit();
}
}
我开发了这个函数,使得可以“沙盒”可能导致致命错误的代码。由于从闭包register_shutdown_function
抛出的异常不会从预先致命错误的调用堆栈中发出,所以我被迫在此函数之后退出,以提供一种统一的使用方式。
function superTryCatchFinallyAndExit( Closure $try, Closure $catch = NULL, Closure $finally )
{
$finished = FALSE;
register_shutdown_function( function() use ( &$finished, $catch, $finally ) {
if( ! $finished ) {
$finished = TRUE;
print "EXPLODE!".PHP_EOL;
if( $catch ) {
superTryCatchFinallyAndExit( function() use ( $catch ) {
$catch( new Exception( "Fatal Error!!!" ) );
}, NULL, $finally );
} else {
$finally();
}
}
} );
try {
$try();
} catch( Exception $e ) {
if( $catch ) {
try {
$catch( $e );
} catch( Exception $e ) {}
}
}
$finished = TRUE;
$finally();
exit();
}
截至PHP 7.4.13,我的经验是一个程序中所有可能的错误和异常都可以通过仅使用两个回调函数来捕获:
set_error_handler("ErrorCB");
set_exception_handler("ExceptCB");
ErrorCB会以任何所需的方式报告其参数并调用Exit()。
ExceptCB在其异常参数上调用“get”方法,并进行一些逻辑来确定文件、行和函数的位置(如果您想了解详细信息,请问我),然后以任何所需的方式报告信息并返回。
唯一需要try/catch的情况是,当@或isset()不足以抑制某些代码的错误时。对于没有设置处理程序的“主函数”使用try/catch会失败,因为它无法捕获所有错误。
如果有人发现生成错误的代码无法被此方法捕获,请告诉我,我将编辑此答案。此方法无法拦截的一个错误是PHP程序末尾附近的单个{字符;这会生成一个Parse error,需要通过包含错误处理的Include文件运行主PHP程序。
我没有发现需要register_shutdown_function()。
请注意,我关心的只是报告错误然后退出程序;我不需要从错误中恢复——那将是一个更困难的问题。