Maison  >  Article  >  développement back-end  >  Parlons de la façon dont les erreurs sont gérées dans PHP7

Parlons de la façon dont les erreurs sont gérées dans PHP7

藏色散人
藏色散人avant
2021-11-01 16:50:432324parcourir

Il y a quelque temps, j'ai rencontré une situation dans le projet que je trouvais étrange à l'époque : lors de l'utilisation de GuzzleHttp pour envoyer une requête curl, la réponse de l'API expirait, provoquant la levée d'une exception. Mais catch(Exception) n'intercepte pas l'exception, ce qui entraîne l'arrêt inattendu du code. Plus tard, j'ai vérifié les informations et découvert que dans PHP 7, l'exception levée par le délai d'attente de la demande GuzzleHttp hérite de l'erreur et que l'erreur n'hérite pas de l'exception, donc catch(Exception) ne peut pas intercepter et gérer l'exception.

Gestion des erreurs dans PHP 7

En PHP 5, lorsqu'une erreur fatale se produit dans le programme, le script s'arrête immédiatement. De plus, le gestionnaire d'erreurs défini via set_error_handler ne sera pas appelé dans ce cas.

【Apprentissage recommandé : Tutoriel PHP7

⒈ Gestionnaire d'erreurs personnalisé set_error_handler

set_error_handler accepte deux paramètres, le premier est une fonction de gestion des erreurs personnalisée et le deuxième paramètre spécifie le déclencheur de la définition automatique de l'erreur. niveau de la fonction de gestion des erreurs. Mais il convient de noter qu’un seul gestionnaire d’erreurs personnalisé peut être actif à la fois.

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;

 Une fois le code ci-dessus exécuté, PHP Remarque : variable non définie : foo sera affichée. Une fois le deuxième set_error_handler exécuté, la fonction de gestion des erreurs personnalisée devient func_error, et en même temps, le niveau d'erreur qui déclenche la fonction de gestion des erreurs personnalisée devient E_ERROR. En PHP, les variables non définies ne déclencheront que des erreurs de niveau E_NOTICE, donc les fonctions personnalisées de gestion des erreurs ne seront pas déclenchées.

Il convient de souligner que la fonction de gestion des erreurs personnalisée ne fonctionne pas sur les niveaux d'erreur suivants :

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

  Parmi les erreurs mentionnées ci-dessus qui ne peuvent pas être gérées par le gestionnaire d'erreurs personnalisé, toute erreur se terminant par ERREUR est une erreur fatale. Bien que les autres types d'erreurs ne soient pas fatales,

  • E_PARSE est une erreur générée lors de l'analyse du code PHP. À l'heure actuelle, le code PHP n'a pas commencé à s'exécuter et le gestionnaire d'erreurs personnalisé ne peut naturellement pas gérer l'erreur.

  • E_CORE_WARNING est généré en PHP Lors de la phase de démarrage de l'initialisation, le code PHP n'est toujours pas en cours d'exécution à ce moment, il ne peut donc pas être géré par le gestionnaire d'erreurs personnalisé

  • E_COMPILE_WARNING est généré lors de la phase de compilation du code PHP , il ne peut donc pas être géré par le gestionnaire d'erreurs personnalisé

Quant à E_STRICT, il s'agit d'une suggestion de modification de code avancée par PHP pour assurer la meilleure interopérabilité et compatibilité ascendante du code. Naturellement, elle ne sera pas traitée par le gestionnaire d'erreurs personnalisé. fonction de gestion des erreurs personnalisée

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

   Le résultat de l'exécution du code ci-dessus est :

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

   Bien qu'il soit défini comme un gestionnaire d'erreurs personnalisé, mais ne fonctionne pas lorsqu'une erreur fatale se produit.

  Pour les erreurs fatales qui ne peuvent pas être gérées par ce gestionnaire d'erreurs personnalisé, dans PHP 5, vous pouvez enregistrer des informations d'erreur spécifiques en enregistrant un rappel d'arrêt (shutdown_function), mais cela se limite à l'enregistrement des informations d'erreur. Lorsqu'une erreur fatale se produit, le code. Il cessera toujours de fonctionner.

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

   L'exécution du code ci-dessus affichera

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
)

⒉ Annuler le gestionnaire d'erreurs personnalisé

  Lorsque plusieurs gestionnaires d'erreurs personnalisés sont définis en même temps, bien que seul le dernier gestionnaire d'erreurs personnalisé défini fonctionnera. Cependant, tous les gestionnaires d'erreurs personnalisés définis seront enregistrés dans une pile (FILO).

 Utilisez restaurer_error_handler pour annuler le gestionnaire d'erreurs personnalisé le plus récemment défini ; si set_error_handler est appelé plusieurs fois en même temps, chaque fois que restaurer_error_handler est appelé, le gestionnaire d'erreurs en haut de la pile sera annulé.

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;

  Lorsque le code ci-dessus est exécuté, le résultat sera :

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

⒊ Gestion des erreurs dans PHP 7

   En PHP 7, lorsqu'une erreur fatale ou une erreur de type E_RECOVERABLE_ERROR se produit, une erreur est généralement générée. Le programme ne se termine pas.

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

   L'exécution du code ci-dessus affichera

Call to a member function method() on string

E_RECOVERABLE_ERROR est une erreur fatale récupérable. L'apparition de cette erreur ne mettra pas le moteur Zend dans un état instable, mais elle doit être détectée et traitée. Si elle n'est pas gérée, cette erreur finira par se transformer en une erreur de type E_ERROR, provoquant éventuellement l'arrêt de l'exécution du code PHP.

  PHP 7, toutes les erreurs fatales ne généreront pas une erreur, certaines erreurs fatales (mémoire insuffisante) qui se produisent dans certaines circonstances entraîneront quand même l'arrêt de l'exécution du code. De plus, si l'erreur générée n'est pas détectée et gérée, le code cessera toujours de s'exécuter.

// 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

  L'erreur dans PHP 7 n'hérite pas de l'exception. La raison en est d'empêcher le code qui intercepte et gère l'exception dans PHP 5 d'attraper ces erreurs. Car en PHP 5, ces erreurs fatales entraîneront l’arrêt de l’exécution du code.

  Error et Exception héritent toutes deux de Throwable. En PHP 7, Throwable est une interface, et tous les objets pouvant être lancés via le mot-clé throw implémentent cette 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 修复。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer