Maison >développement back-end >PHP7 >Discutez des mécanismes de gestion des erreurs et des exceptions de PHP

Discutez des mécanismes de gestion des erreurs et des exceptions de PHP

coldplay.xixi
coldplay.xixiavant
2020-06-22 17:50:193418parcourir

Discutez des mécanismes de gestion des erreurs et des exceptions de PHP

Avertissement : cet article est sous licence CC BY-NC-ND 4.0.

Le PHP d'origine n'avait que des erreurs et aucune exception. En regardant certains anciens documents, vous pouvez voir qu'un grand nombre d'erreurs sont directement renvoyées à la balise html. Les frameworks modernes ont déjà tout bouclé, et vous pouvez avoir une plus belle page d'affichage des erreurs en lançant directement une exception, comme les meilleures erreurs des rails. Bien entendu, les frameworks modernes pour PHP, comme Laravel, ont également fait du bon travail. Cependant, notre société utilise encore actuellement CodeIgniter 2, et sa gestion des erreurs et des exceptions est encore relativement rudimentaire. en passant à L’opportunité de PHP7 était de trier les mécanismes de gestion des erreurs et des exceptions de PHP.

Tutoriel recommandé : "Tutoriel PHP"

Erreurs et exceptions PHP

PHP5 a implémenté la gestion des exceptions, qui n'est pas différente des autres langages Big. , ce n'est rien de plus que d'essayer, d'attraper, de ne pas attraper, n'appuyez pas dessus, parlons d'abord de l'erreur.

Erreurs PHP

En plus des exceptions, les erreurs les plus courantes générées par PHP5 sont les erreurs. Vous pouvez trouver les définitions de toutes les erreurs dans la documentation officielle. Ces erreurs peuvent être grossièrement divisées en AVERTISSEMENT, ERREUR (erreur fatale), AVIS, etc. 1. L'article Résumé du mécanisme d'erreur de PHP donne les scénarios dans lesquels chaque erreur se produit.

E_DEPRECATED(8192) La notification d'exécution, lorsqu'elle est activée, donnera un avertissement pour le code qui pourrait ne pas fonctionner correctement dans les versions futures.

E_USER_DEPRECATED(16384) est généré par l'utilisateur à l'aide de la fonction PHP trigger_error() dans le code

E_NOTICE(8) notification d'exécution. Indique que le script rencontre une situation qui peut apparaître comme une erreur

E_USER_NOTICE(1024) est un message de notification généré par l'utilisateur à l'aide de la fonction trigger_error() de PHP dans le code

E_WARNING(2) Avertissement d'exécution (erreur non fatale)

E_USER_WARNING(512) Généré par l'utilisateur à l'aide de la fonction trigger_error() de PHP dans le code

E_CORE_WARNING(32) Avertissements d'initialisation PHP généré par le cœur du moteur PHP lors du démarrage

E_COMPILE_WARNING(128) Le moteur de script Zend a généré des avertissements au moment de la compilation

E_ERROR(1) Erreur d'exécution fatale

E_USER_ERROR(256) Généré par l'utilisateur à l'aide de la fonction trigger_error() de PHP dans le code

E_CORE_ERROR(16) Erreur fatale générée par le cœur du moteur PHP lors du processus de démarrage de l'initialisation PHP

E_COMPILE_ERROR( 64) Erreur fatale de compilation générée par le moteur de script Zend

E_PARSE(4) Erreur d'analyse de syntaxe au moment de la compilation. Les erreurs d'analyse sont générées uniquement par l'analyseur

E_STRICT(2048) Active les suggestions de PHP pour les modifications de code afin d'assurer la meilleure interopérabilité et compatibilité ascendante du code

E_RECOVERABLE_ERROR(4096) Erreur fatale pouvant être détectée. Cela indique qu'une erreur potentiellement dangereuse s'est produite, mais n'a pas rendu le moteur PHP instable. Si l'erreur n'est pas détectée par un gestionnaire défini par l'utilisateur (voir set_error_handler() ), elle deviendra un E_ERROR et le script se terminera.

E_ALL(30719) Tous les messages d'erreur et d'avertissement (le manuel dit qu'il ne contient pas E_STRICT, mais après test, il contient en fait E_STRICT).

Les plus courants sont :

<?php
// E_ERROR
nonexist(); // PHP Fatal error:  Call to undefined function nonexist()
throw new Exception(&#39;&#39;); // 未捕获异常也是 fatal error

// E_NOTICE
$a = $b; //  PHP Notice:  Undefined variable
$a = []; $a[2]; // PHP Notice:  Undefined offset: 2

// E_WARNNING
require &#39;nonexist.php&#39; // warning and fatal error

Pour des raisons historiques, cet ancien framework ci2 comporte de nombreuses choses déraisonnables, comme la lecture de fichiers journaux inexistants ; nous avons également des utilisations irrégulières de PHP, telles que :

<?php
$req = [];
$user_id = $req[&#39;user_id&#39;]; // PHP error:  Undefined offset
if (null === $user_id) { /* do something */}
 ;

De nombreux endroits dans notre code reposent sur la performance d'obtention de null lorsqu'une clé inexistante est obtenue, et chaque fois qu'elle est utilisée de cette manière, il y aura une erreur E_NOTICE. Bien que vous puissiez utiliser array_exists pour faire if else, c'est après tout plus gênant. Après PHP7, vous pouvez utiliser des structures de données claires telles que Map, Set et Vector via des plug-ins de structure de données pour mieux résoudre ce problème.

Gestion des erreurs PHP

Si aucune configuration n'est effectuée, les erreurs PHP seront imprimées directement. Les anciennes applications PHP faisaient cela. Mais les applications modernes ne peuvent évidemment pas faire cela. Les erreurs dans les applications modernes doivent suivre les règles suivantes 2 :

Assurez-vous de laisser PHP signaler les erreurs ; environnement de développement, afficher les erreurs ;

ne peut pas afficher les erreurs dans les environnements de production

enregistrer les erreurs dans les environnements de développement et de production ;

Dans un environnement de production, les erreurs ne peuvent pas être imprimées directement. Elles doivent être enregistrées dans le fichier journal et un message d'erreur général renvoyé à l'utilisateur. La fonction set_error_handler consiste à définir une fonction de gestion des erreurs définie par l'utilisateur pour gérer les erreurs qui se produisent dans le script. Nous pouvons écrire les informations d'erreur dans le fichier journal dans cette fonction et renvoyer les informations d'erreur de manière uniforme.

本来这个函数是搭配 trigger_error 函数使用的。用户通过 trigger_error 产生 error,然后用 error_handler 来处理错误。只是在这种场景下往往「异常」更好用,所以这么用的并不多。

在前述的系统自带的 16 种错误中,有一部分相当重要的错误并不能被 error_handler 捕获3

以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、E_COMPILE_WARNING,和在调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。

这些错误将无法记录下来,同时也不方便统一处理4。在 PHP7 之前的 PHP 版本一个很大的痛点就是:发生了 E_ERROR 错误,无法捕获,导致数据库的事务无法回滚造成数据不一致5

另外一个需要注意的是, error_handler 处理完毕,脚本将会继续执行发生错误的后一行。在某些情况下,你可能希望遇到某些错误可以中断脚本的执行。在官方文档中已说明,

同时注意,在需要时你有责任使用 die()。 如果错误处理程序返回了,脚本将会继续执行发生错误的后一行。

也就是说,某些情况下,我们处理完 E_WARNING 之后,需要及时退出脚本(即 die() 或者 exit())。

PHP 异常

异常是对程序错误的一种优秀的处理方式,较于错误,异常的优点是默认打印调用栈,便于调试,可控等,可以参考一下鸟哥的文章我们什么时候应该使用异常,清晰的点明了错误码和异常的优缺点。

对异常的处理也要遵循前述的错误处理规则2。在我们的日常开发中,不可能保证可以 catch 所有的异常,而未被 catch 的异常将以 fatal error 的形式中断脚本的执行并输出错误信息。所以要借助 set_exception_handler,统一处理所有未被 catch 的异常。我们可以像 error_handler 那样,在 exception_handler 中处理 log,将数据库的事务回滚。

前面提到,error_handler 需要在必要的时候手动中断脚本, PHP 文档中给出的一种实践是,在 error_handler 中 throw ErrorException,代码示例如下:

<?php
function exception_error_handler($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        // This error code is not included in error_reporting
        return;
    }
    throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");

/* Trigger exception */
strpos();

这样凡是不想忽略的 error,都会以 Uncaught ErrorException 的形式返回并中断脚本。

PHP 异常机制

鸟哥通过一个例子讲解了 PHP 的异常的处理机制,在这里转述一下。

<?php
function onError($errCode, $errMesg, $errFile, $errLine) {
    echo "Error Occurred\n";
    throw new Exception($errMesg);
}
 
function onException($e) {
    echo &#39;********exception: &#39; . $e->getMessage();
}
 
set_error_handler("onError");
 
set_exception_handler("onException");

require("nonexist.php");
   

其运行结果为

  1. Error Occurred
  2. PHP Fatal error

而 onException 并没有执行到,说明在 error_handler 中 throw exception 不会被 exception_handler 截获。

require 不存在的文件会抛出两个错误,

  1. WARNING : 在PHP试图打开这个文件的时候抛出
  2. E_COMPILE_ERROR : 从PHP打开文件的函数返回失败以后抛出

PHP 中的异常处理机制如下:

   

而PHP在遇到 Fatal Error 的时候,会直接 zend_bailout,而 zend_bailout 会导致程序流程直接跳过上面代码段,也可以理解为直接 exit 了(longjmp),这就导致了 user_exception_handler 没有机会发生作用。

PHP 错误分类

综上所述,在 PHP 中,错误和异常可以分为以下 3 个类别:异常,可截获错误,不可截获错误。异常和可截获错误虽然机理不同,但可以当做是同一种处理方式,而不可截获错误是另一种,是一种较为棘手的错误类型。马上将会讲到,PHP7 中的 fatal error 是一种继承自 Throwable 的 Error,是可以被 try catch 住的。通过这一方式 PHP7 解决了这一难题。

PHP7 的错误和异常

PHP 7 改变了大多数错误的报告方式。不同于传统(PHP 5)的错误报告机制,现在大多数错误被作为 Error 异常抛出(在 PHP7 中,只有 fatal error 和 recoverable error 抛出异常,其他 error 比如 warning 和 notice 的表现不变6)。PHP7 中的 Error 和 Exception 的关系如图        6

interface Throwable
    |- Exception implements Throwable
        |- ...
    |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- ArithmeticError extends Error
            |- pisionByZeroError extends ArithmeticError
        |- AssertionError extends Error
   

值得注意的是,Error 类表现上和 Exception 基本一致,可以像 Exception 异常一样被第一个匹配的 try / catch 块所捕获,如果没有匹配的 catch 块,则调用异常处理函数(事先通过        set_exception_handler() 注册7)进行处理。 如果尚未注册异常处理函数,则按照传统方式处理,被报告为一个致命错误(Fatal Error)。但并非继承自 Exception 类(要考虑到和 PHP5 的兼容性),所以不能用 catch (Exception        $e) { ... } 来捕获,而需要使用 catch (Error $e) { ... },当然,也可以使用 set_exception_handler 来捕获。

但是,用户不能自己定义类实现 Throwable,这是为了保证只有 Exception 和 Error 才可以抛出。

PHP7 的 ERROR 处理

PHP7 中的 fatal error 会抛出 Error,且可以被正常 catch 到:

<?php
$a = 1;
try {
  $a->nonexist();
} catch (Error $e) {
  // Handle error
}
   

也有些错误场景下会抛出更加详细的错误,比如:

<?php
// TypeError
function test(int $i) {
  echo $i;
}
try {
  test(&#39;test&#39;);
} catch (TypeError $e) {
  // Handle error
}

// ParseError
try{
  eval(&#39;i=1;&#39;);
} catch (ParseError $e) { 
  echo $e->getMessage(), "\n";
}

// ArithmeticError
try {
    $value = 1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage(), "\n";
}

// pisionByZeroError
try {
    $value = 1 % 0;
} catch (pisionByZeroError $e) {
    echo $e->getMessage(), "\n";
}
   

Error 和 Exception 的选择

当需要自定义处理错误的时候,应该选择继承 Error 还是 Exception 呢?

我们注意到,PHP7 中是将曾经的 fatal error 变成了 Error 抛出,而 fatal error 一般都是一些不需要在运行时处理的错误,这种错误旨在提醒程序员,这里的代码写的有问题,需要修复,而不是逻辑上要 catch 它做某些业务。

因此,绝大多数情况下,我们并不需要继承 Error,甚至 catch Error 也不常见,只在某些需要 log,回滚数据库,清理现场等场合才需要这样做。

对错误和异常的一种实践

根据以上所述,我们提炼了一个对错误和异常处理较好的实践。

  1. 对于业务中不应该出现错误的地方,抛出 InternalException,而不是 Error
<?php
class InternalException extends Exception { /*...*/ }

function find(Array $ids) {
  if (empty($ids)) {
    throw new InternalException('ids should not be empty');
  }
  ...
}
   
  1. 只在需要清理现场的时候 catch Error
<?php
try { /*...*/ }
catch (Throwable $t) {
  // log, transaction rollback, cleanup...
}
   
  1. 未捕获的 Error 和 Exception 通过 set_exception_handler 做后续清理和 log
  2. 其他错误仍然通过 set_error_handler 来处理,在处理的时候使用更加明确的 FriendlyErrorType,并抛出 ErrorException 记录调用栈

FriendlyErrorType:

<?php
function FriendlyErrorType($type) 
{ 
    switch($type) 
    { 
        case E_ERROR: // 1 // 
            return 'E_ERROR'; 
        case E_WARNING: // 2 // 
            return 'E_WARNING'; 
        case E_PARSE: // 4 // 
            return 'E_PARSE'; 
        case E_NOTICE: // 8 // 
            return 'E_NOTICE'; 
        case E_CORE_ERROR: // 16 // 
            return 'E_CORE_ERROR'; 
        case E_CORE_WARNING: // 32 // 
            return 'E_CORE_WARNING'; 
        case E_COMPILE_ERROR: // 64 // 
            return 'E_COMPILE_ERROR'; 
        case E_COMPILE_WARNING: // 128 // 
            return 'E_COMPILE_WARNING'; 
        case E_USER_ERROR: // 256 // 
            return 'E_USER_ERROR'; 
        case E_USER_WARNING: // 512 // 
            return 'E_USER_WARNING'; 
        case E_USER_NOTICE: // 1024 // 
            return 'E_USER_NOTICE'; 
        case E_STRICT: // 2048 // 
            return 'E_STRICT'; 
        case E_RECOVERABLE_ERROR: // 4096 // 
            return 'E_RECOVERABLE_ERROR'; 
        case E_DEPRECATED: // 8192 // 
            return 'E_DEPRECATED'; 
        case E_USER_DEPRECATED: // 16384 // 
            return 'E_USER_DEPRECATED'; 
    } 
    return ""; 
}
   

error_handler:

<?php
function exception_error_handler($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        // This error code is not included in error_reporting
        return;
    }
 	log FriendlyErrorType($severity);
    throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");
   
  1. PHP中的错误级别与具体报错信息分类 ↩

  2. PHP 最佳实践之异常和错误 ↩ ↩2

  3. E_ERROR 无法捕获,E_RECOVERABLE_ERROR 可以,后者默认输出 Catachable fatal error ↩

  4. fatal error 会记录到 web 服务器的 error.log,这一点需要注意,因为这个 log 的位置往往不是 PHP 应用定义的,而是 web 服务器定义的。 ↩

  5. PHP 中还有一个 register_shutdown_function 函数,它允许注册一个会在 PHP 中止时执行的函数,这个函数可以捕获 fatal error,毕竟是只要是脚本中断就可以捕获的。ci2 并没有使用这个方法,所以相关问题一直没有得到很好的解决,当时也没有意识到这个函数的存在,升级 PHP7 之后可以通过                catch Error 来解决,便不再需要这样处理了。 ↩

  6. Throwable Exceptions and Errors in PHP 7 ↩ ↩2

  7. 在 PHP7 中,传入 exception_handler 的参数从 Exception 改为 Throwable,这意味着 exception_handler 可以截获 Error。 ↩

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