Home >Backend Development >PHP7 >Let's talk about how Error is handled in PHP7

Let's talk about how Error is handled in PHP7

藏色散人
藏色散人forward
2021-11-01 16:50:432375browse

Some time ago, I encountered a situation in the project that I thought was strange at the time: when using GuzzleHttp to send a curl request, the API response timed out, causing an exception to be thrown. But catch(\Exception) does not catch the exception, causing the code to stop running unexpectedly. Later, I checked the information and found that in PHP 7, the exception thrown by GuzzleHttp request timeout inherits Error, and Error does not inherit Exception, so catch(\Exception) cannot catch and handle the exception.

Handling of Error in PHP 7

In PHP 5, when a fatal error occurs in the program, the script will stop running immediately. Also, the error handler set via set_error_handler will not be called in this case.

【Recommended learning: PHP7 tutorial

⒈ Custom error handler set_error_handler

set_error_handler accepts two Parameters, the first one is a custom error handling function, and the second parameter specifies the error level that triggers the custom error handling function. But it should be noted that only one custom error handler can be active at any time.

function func_notice($num, $str, $file, $line) {
    print "Encountered notice $num in $file, line $line: $str\n";
}
function func_error($num, $str, $file, $line) {
    print "Encountered error $num in $file, line $line: $str\n";
}
set_error_handler("func_notice", E_NOTICE);
set_error_handler("func_error", E_ERROR);
echo $foo;

 After the above code is executed, PHP Notice: Undefined variable: foo will be output. After the second set_error_handler is executed, the custom error handling function becomes func_error, and at the same time, the error level that triggers the custom error handling function becomes E_ERROR. In PHP, undefined variables will only trigger E_NOTICE level errors, so custom error handling functions will not be triggered.

It should be pointed out that the custom error handling function does not work on the following error levels:

E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING、E_STRICT

 Among the above-mentioned errors that cannot be handled by the custom error handler, Anything ending with ERROR is a fatal error. Although the other several types of errors are not fatal,

  • E_PARSE is an error generated when parsing the PHP code. At this time, the PHP code has not started running, and the custom error handler naturally cannot handle the error.

  • E_CORE_WARNING is generated during the initial startup phase of PHP. At this time, the PHP code is still not running, so it cannot be handled by the custom error handler

  • E_COMPILE_WARNING is generated during the compilation phase of PHP code, so it cannot be handled by a custom error handler

. As for E_STRICT, PHP is designed to ensure the best interoperability and forward compatibility of the code. The proposed code modification suggestions will naturally not be processed by the custom error handling function

function func_error($num, $str, $file, $line) {
    print "Encountered error $num in $file, line $line: $str\n";
}
set_error_handler('func_error', E_NOTICE);
$obj = 'foo';
$obj->method();

 The output result of running the above code is:

PHP Fatal error:  Call to a member function method() on string

 Although a custom error handler is set, the fatal error occurs When it happens, it doesn't work.

For fatal errors that cannot be handled by this custom error handler, in PHP 5 you can register a shutdown callback (shutdown_function) to record specific error information, but it is limited to recording error information when it occurs The code will still stop running on a fatal error.

$shutdownHandler = function(){
    print PHP_EOL;
    print "============================" . PHP_EOL;
    print "Running the shutdown handler" . PHP_EOL;
    $error = error_get_last();
    if (!empty($error))
    {
        print "Looks like there was an error: " . print_r($error, true) . PHP_EOL;
        // 可以添加记录日志的逻辑
    }
    else
    {
        // 程序正常运行结束
        print "Running a normal shutdown without error." . PHP_EOL;
    }
};
register_shutdown_function($shutdownHandler);
$obj = 'foo';
$obj->method();

 The execution of the above code will output

PHP Fatal error:  Call to a member function method() on string in /home/chenyan/test.php on line 24
============================
Running the shutdown handler
Looks like there was an error: Array
(
    [type] => 1
    [message] => Call to a member function method() on string
    [file] => /home/chenyan/test.php
    [line] => 24
)

⒉ Undo the custom error handler

 When multiple custom error handlers are set at the same time, Although only the last set custom error handler works. However, all set custom error handlers will be saved in a stack (FILO).

Use restore_error_handler to undo the most recently set custom error handler; if set_error_handler is called multiple times at the same time, each time restore_error_handler is called, the error handler on the top of the stack will be undoed.

function func_notice($num, $str, $file, $line) {
    print "Encountered notice : $str\n";
}
set_error_handler("func_notice", E_NOTICE);
set_error_handler("func_notice", E_NOTICE);
set_error_handler("func_notice", E_NOTICE);
echo $foo;
set_error_handler("func_notice", E_NOTICE);
echo $foo;
restore_error_handler();
echo $foo;
restore_error_handler();
echo $foo;
restore_error_handler();
echo $foo;
restore_error_handler();
echo $foo;

 When the above code is run, the output will be:

Encountered notice : Undefined variable: foo
Encountered notice : Undefined variable: foo
Encountered notice : Undefined variable: foo
Encountered notice : Undefined variable: foo
Encountered notice : Undefined variable: foo
PHP Notice:  Undefined variable: foo

⒊ Error handling in PHP 7

 In PHP 7, when When a fatal error or an error of type E_RECOVERABLE_ERROR occurs, an Error is usually thrown and the program does not terminate.

try {
    $obj = 'foo';
    $obj->method();
} catch (\Error $e) {
    echo $e->getMessage();
}

 Running the above code will output

Call to a member function method() on string

E_RECOVERABLE_ERROR is a trappable fatal error. The occurrence of this error will not make the Zend engine unstable, but it must be caught. And deal with it. If not handled, this error will eventually turn into an E_ERROR type error, eventually causing the PHP code to stop running.

In PHP 7, not all fatal errors will throw Error. Fatal errors (Out Of Memory) that occur under certain circumstances will still cause the code to stop running. Additionally, if the thrown Error is not caught and handled, the code will still stop running.

// bak.sql 的大小为 377 M
// PHP 配置的 memory_limit = 128M
try {
    $file = './bak.sql';
    file_get_contents($file);
} catch (\Error $e) {
    echo $e->getMessage();
}
// 执行以上代码,仍然会产生致命错误
PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 395191240 bytes)
// 抛出的 Error 没有被捕获并处理,代码依然会停止运行
$obj = 'foo';
$obj->method();
// 执行以上代码,由于并没有用 try/catch 捕获并处理抛出的 Error,程序仍然会停止运行
PHP Fatal error:  Uncaught Error: Call to a member function method() on string

 Error in PHP 7 does not inherit Exception. The reason for this is to prevent the code that catches and handles Exception in PHP 5 from catching these Errors. Because in PHP 5, these fatal errors will cause the code to stop running.

Both Error and Exception inherit from Throwable. In PHP 7, Throwable is an interface, and all objects that can be thrown through the throw keyword implement this interface.

interface Throwable
{
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getTraceAsString(): string;
    public function getPrevious(): Throwable;
    public function __toString(): string;
}

  需要指出的是,Throwable 是 PHP 底层的 interface,PHP 代码中不能直接实现 Throwable 。之所以作出这个限制,是因为通常只有 Error 和 Exception 可以被抛出,并且这些抛出的 Error 和 Exception 中还存储了它们被抛出的堆栈跟踪信息,而 PHP 代码中开发者自定义的 class 无法实现这些。

  要在 PHP 代码中实现 Throwable 必须通过继承 Exception 来实现。

interface CustomThrowable extends Throwable {}
class CustomException extends Exception implements CustomThrowable {}
throw new CustomException();

  PHP 7 中 Error 和 Exception 的继承关系

interface Throwable
    |- Exception implements Throwable
        |- Other Exception classes
    |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- AssertionError extends Error
        |- ArithmeticError extends Error
            |- DivizionByZeroError extends ArithmeticError
  • TypeError

  当函数的传参或返回值的数据类型与申明的数据类型不一致时,会抛出 TypeError

function add(int $left, int $right)
{
    return $left + $right;
}
try {
    $value = add('left', 'right');
} catch (TypeError $e) {
    echo $e->getMessage();
}
// 运行以上代码,会输出:
Argument 1 passed to add() must be of the type int, string given

  当开启严格模式时,如果 PHP 内建函数的传参个数与要求的参数不一致,也会抛出 TypeError

declare(strict_types = 1);
try {
    substr('abc');
} catch (TypeError $e) {
    echo $e->getMessage();
}
// 运行以上代码,会输出:
substr() expects at least 2 parameters, 1 given

  默认情况下,PHP 7 处于弱模式。在弱模式下,PHP 7 会尽可能的将传参的数据类型转换为期望的数据类型。例如,如果函数期望的参数类型为 string,而实际传参的数据类型的 int,那么 PHP 会把 int 转换为 string。

// declare(strict_types = 1);
function add(string $left, string $right)
{
    return $left + $right;
}
try {
    $value = add(11, 22);
    echo $value;
} catch (TypeError $e) {
    echo $e->getMessage();
}
// 以上代码运行,会正常输出 33,PHP 会对传参的数据类型做转换(int→string→int)
// 但如将 PHP 改为严格模式,则运行是会抛出 TypeError
Argument 1 passed to add() must be of the type string, int given
  • ParseError

  当在 include 或 require 包含的文件中存在语法错误,或 eval() 函数中的代码中存在语法错误时,会抛出 ParseError

// a.php
$a = 1
$b = 2
// test.php
try {
    require 'a.php';
} catch (ParseError $e) {
    echo $e->getMessage();
}
// 以上代码运行会输出:
syntax error, unexpected '$b' (T_VARIABLE)
// eval 函数中的代码存在语法错误
try {
    eval("$a = 1");
} catch (ParseError $e) {
    echo $e->getMessage();
}
// 以上代码运行会输出:
syntax error, unexpected end of file
  • AssertionError

  当断言失败时,会抛出 AssertionError(此时要求 PHP 配置中 zend.assertions = 1,assert.exception = 1,这两个配置可以在 php.ini 文件中配置,也可以通过 ini_set() 在 PHP 代码中配置)。

ini_set('zend_assertions', 1);
ini_set('assert.exception', 1);
try {
    $test = 1;
    assert($test === 0);
} catch (AssertionError $e) {
    echo $e->getMessage();
}
// 运行以上代码会输出:
assert($test === 0)
  • ArithmeticError

  在 PHP 7 中,目前有两种情况会抛出 ArithmeticError:按位移动操作,第二个参数为负数;使用 intdiv() 函数计算 PHP_INT_MIN 和 -1 的商(如果使用 / 计算 PHP_INT_MIN 和 -1 的商,结果会自动转换为 float 类型)。

try {
    $value = 1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}
// 运行以上代码,会输出:
Bit shift by negative number
try {
    $value = intdiv(PHP_INT_MIN, -1);
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}
// 运行以上代码,会输出:
Division of PHP_INT_MIN by -1 is not an integer
  • DivisionByZeroError

  抛出 DivisionByZeorError 的情况目前也有两种:在进行取模(%)运算时,第二个操作数为 0;使用 intdiv() 计算两个数的商时,除数为 0。如果使用 / 计算两个数的商时除数为 0,PHP 只会产生一个 Warning。并且,如果被除数非 0,则结果为 INF,如果被除数也是 0,则结果为 NaN。

try {
    $value = 1 % 0;
    echo $value;
} catch (DivisionByZeroError $e) {
    echo $e->getMessage(), "\n";
}
// 运行以上代码,会输出:
Modulo by zero
try {
    $value = intdiv(0, 0);
    echo $value;
} catch (DivisionByZeroError $e) {
    echo $e->getMessage(), "\n";
}
// 运行以上代码,会输出:
Division by zero

  通常在实际的业务中,捕获并处理抛出的 Error 并不常见,因为一旦抛出 Error 说明代码存在严重的 BUG,需要修复。所以,在实际的业务中,Error 更多的只是被用来捕获并记录具体的错误日志,然后通知开发者进行 BUG 修复。

The above is the detailed content of Let's talk about how Error is handled in PHP7. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.im. If there is any infringement, please contact admin@php.cn delete