>  기사  >  백엔드 개발  >  PHP의 오류 및 예외 처리 메커니즘에 대해 토론합니다.

PHP의 오류 및 예외 처리 메커니즘에 대해 토론합니다.

coldplay.xixi
coldplay.xixi앞으로
2020-06-22 17:50:193287검색

PHP의 오류 및 예외 처리 메커니즘에 대해 토론합니다.

면책 조항: 이 기사는 CC BY-NC-ND 4.0에 따라 라이센스가 부여됩니다.

원래 PHP에는 오류만 있었고 예외는 없었습니다. 일부 오래된 문서를 보면 많은 오류 출력이 html 태그에 직접 반영되는 것을 볼 수 있습니다. 최신 프레임워크는 이미 모든 것을 마무리했으며 레일의 더 나은 오류와 같은 예외를 직접 발생시켜 더 아름다운 오류 표시 페이지를 가질 수 있습니다. 물론, laravel과 같은 최신 PHP 프레임워크도 좋은 성과를 거두었습니다. 그러나 우리 회사는 현재 여전히 codeigniter 2를 사용하고 있으며 오류 및 예외 처리는 여전히 비교적 조잡합니다. 업그레이드하여 PHP7의 기회는 PHP의 오류 및 예외 처리 메커니즘을 정리하는 것이었습니다.

추천 튜토리얼: "PHP 튜토리얼"

PHP 오류 및 예외

PHP5는 예외 처리를 구현했는데, 이는 다른 언어와 크게 다르지 않습니다. , 먼저 나열되지 않습니다. 틀렸다고 말하세요.

PHP 오류

예외 외에도 PHP5에서 발생하는 가장 일반적인 것은 오류입니다. 공식 문서에서 모든 오류에 대한 정의를 찾을 수 있습니다. 이러한 오류는 크게 WARNING, ERROR(치명적인 오류), NOTICE 등으로 나눌 수 있습니다. 1. PHP 오류 메커니즘 요약 문서에서는 각 오류가 발생하는 시나리오를 제공합니다.

E_DEPRECATED(8192) 런타임 알림이 활성화되면 향후 버전에서 제대로 작동하지 않을 수 있는 코드에 대해 경고를 표시합니다.

E_USER_DEPRECATED(16384)는 사용자가

E_NOTICE(8) 런타임 알림을 생성하는 코드에서 PHP 함수 Trigger_error()를 사용하여 생성됩니다. 스크립트에서 오류로 나타날 수 있는 상황이 발생했음을 나타냅니다.

E_USER_NOTICE(1024)는 코드에서 PHP의 Trigger_error() 함수를 사용하여 사용자가 생성한 알림 메시지입니다.

E_WARNING(2) 런타임 경고(비) -치명적인 오류) )

E_USER_WARNING(512) 코드에서 PHP의 Trigger_error() 함수를 사용하여 사용자에 의해 생성됨

E_CORE_WARNING(32) PHP 초기화 및 시작 중 PHP 엔진 코어에서 생성된 경고

E_COMPILE_WARNING(128) 다음에 의해 생성됨 Zend 스크립트 엔진 컴파일 시간 경고

E_ERROR(1) 치명적인 런타임 오류

E_USER_ERROR(256) 사용자가 코드에서 PHP의 Trigger_error() 함수를 사용하여 PHP 초기화 시작 프로세스 중에

E_CORE_ERROR(16)을 생성합니다. . PHP 엔진 코어에서 생성된 치명적인 오류

E_COMPILE_ERROR(64) Zend 스크립트 엔진에서 생성된 치명적인 컴파일 시간 오류

E_PARSE(4) 컴파일 시간 구문 분석 오류입니다. 구문 분석 오류는 분석기에 의해서만 생성됩니다

E_STRICT(2048) 코드 변경에 대한 PHP의 제안을 활성화하여 코드의 최고의 상호 운용성과 향후 호환성을 보장합니다

E_RECOVERABLE_ERROR(4096) 포착 가능한 치명적인 오류입니다. 잠재적으로 위험한 오류가 발생했지만 PHP 엔진이 불안정해지지는 않았음을 나타냅니다. 사용자 정의 핸들러( set_error_handler() 참조)에서 오류를 포착하지 못하면 E_ERROR가 되고 스크립트가 종료됩니다.

E_ALL(30719) 모든 오류 및 경고 메시지(설명서에는 E_STRICT가 포함되어 있지 않다고 되어 있지만 테스트 후에는 실제로 E_STRICT가 포함되어 있습니다).

일반적인 것들은 다음과 같습니다:

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

역사적인 이유로 인해 이 오래된 ci2 프레임워크에는 존재하지 않는 로그 파일을 읽는 등 불합리한 일이 많이 있습니다. 또한 다음과 같이 PHP를 불규칙하게 사용합니다.

우리 코드의 많은 부분은 존재하지 않는 키를 얻을 때 null을 얻는 이런 종류의 성능에 의존하며, 이런 식으로 사용될 때마다 E_NOTICE 오류가 발생합니다. else_exists를 사용하여 else를 수행할 수 있지만 결국에는 더 번거롭습니다. PHP7 이후에는 데이터 구조 플러그인을 통해 Map, Set, Vector 등의 명확한 데이터 구조를 사용하면 이 문제를 더 잘 해결할 수 있습니다.

PHP 오류 처리

구성이 이루어지지 않으면 PHP 오류가 직접 인쇄됩니다. 예전 PHP 애플리케이션은 실제로 이런 일을 했습니다. 그러나 최신 애플리케이션에서는 분명히 이 작업을 수행할 수 없습니다.

2:

PHP에서 오류를 보고하도록 해야 합니다.

오류는 개발 환경에 표시되어야 합니다. 프로덕션 환경에서;

개발 및 프로덕션 환경 모두에서 오류를 기록합니다.

프로덕션 환경에서는 오류를 직접 인쇄할 수 없습니다. 로그 파일에 기록하고 일반 오류 메시지를 사용자에게 반환해야 합니다. set_error_handler 함수는 스크립트에서 발생하는 오류를 처리하기 위해 사용자가 정의한 오류 처리 함수를 설정하는 것입니다. 이 함수에서는 로그 파일에 오류 정보를 기록하고 오류 정보를 균일하게 반환할 수 있습니다.

本来这个函数是搭配 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。 ↩

위 내용은 PHP의 오류 및 예외 처리 메커니즘에 대해 토론합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제