얼마 전 프로젝트에서 이상한 상황에 직면했습니다. GuzzleHttp를 사용하여 컬 요청을 보낼 때 API 응답 시간이 초과되어 예외가 발생했습니다. 그러나 catch(Exception)은 예외를 포착하지 못하여 코드 실행이 예기치 않게 중지됩니다. 나중에 정보를 확인해보니 PHP 7에서는 GuzzleHttp 요청 시간 초과로 발생한 예외가 Error를 상속하고 Error는 Exception을 상속하지 않으므로 catch(Exception)가 예외를 포착하고 처리할 수 없다는 사실을 발견했습니다.
PHP 7의 오류 처리
PHP 5에서는 프로그램에 치명적인 오류가 발생하면 스크립트 실행이 즉시 중지됩니다. 또한 이 경우 set_error_handler를 통해 설정된 오류 핸들러가 호출되지 않습니다.
【추천 학습: PHP7 tutorial】
⒈ 사용자 정의 오류 처리기 set_error_handler
set_error_handler는 두 개의 매개변수를 허용합니다. 첫 번째 매개변수는 사용자 정의 오류 처리 함수이고, 두 번째 매개변수는 자동 오류 정의에 대한 트리거를 지정합니다. 오류 처리 기능 수준. 그러나 언제든지 하나의 사용자 정의 오류 핸들러만 활성화될 수 있다는 점에 유의해야 합니다.
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;
위 코드가 실행되면 PHP 주의사항: 정의되지 않은 변수: foo가 출력됩니다. 두 번째 set_error_handler가 실행된 후 사용자 정의 오류 처리 함수는 func_error가 되고, 동시에 사용자 정의 오류 처리 함수를 트리거하는 오류 수준은 E_ERROR가 됩니다. PHP에서 정의되지 않은 변수는 E_NOTICE 수준 오류만 트리거하므로 사용자 정의 오류 처리 기능은 트리거되지 않습니다.
다음 오류 수준에서는 사용자 정의 오류 처리 기능이 작동하지 않는다는 점에 유의해야 합니다.
E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING、E_STRICT
위에서 언급한 오류 중 사용자 정의 오류 처리기로 처리할 수 없는 오류 중 ERROR로 끝나는 오류는 치명적인 실수입니다. 다른 여러 유형의 오류는 치명적이지는 않지만
E_PARSE는 PHP 코드를 구문 분석할 때 생성되는 오류입니다. 이때 PHP 코드가 실행되지 않으며 사용자 정의 오류 핸들러는 당연히 오류를 처리할 수 없습니다.
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();위 코드를 실행한 결과는 다음과 같습니다.
PHP Fatal error: Call to a member function method() on string사용자 정의 오류 처리기로 설정되어 있지만 치명적인 오류가 발생하면 작동하지 않습니다. 이 사용자 정의 오류 핸들러로 처리할 수 없는 치명적인 오류의 경우 PHP 5에서는 종료 콜백(shutdown_function)을 등록하여 특정 오류 정보를 기록할 수 있지만, 치명적인 오류가 발생하면 오류 정보 기록으로 제한됩니다. 여전히 실행이 중지됩니다.
$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();위 코드를 실행하면 다음이 출력됩니다.
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 )
⒉ 사용자 정의 오류 핸들러 실행 취소
여러 사용자 정의 오류 핸들러가 동시에 설정되면 마지막으로 설정된 사용자 정의 오류 핸들러만 작동합니다. 그러나 설정된 모든 사용자 정의 오류 핸들러는 스택(FILO)에 저장됩니다. restore_error_handler를 사용하여 가장 최근에 설정된 사용자 정의 오류 핸들러를 실행 취소합니다. set_error_handler가 동시에 여러 번 호출되는 경우, Restore_error_handler가 호출될 때마다 스택 상단에 있는 오류 핸들러가 실행 취소됩니다.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;위 코드가 실행되면 출력은 다음과 같습니다.
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
⒊ PHP 7의 오류 처리
try { $obj = 'foo'; $obj->method(); } catch (\Error $e) { echo $e->getMessage(); }위 코드를 실행하면
Call to a member function method() on stringE_RECOVERABLE_ERROR가 출력됩니다. 이 오류가 발생해도 Zend 엔진이 불안정한 상태가 되지는 않지만 이를 포착하여 처리해야 합니다. 처리하지 않은 채로 두면 이 오류는 결국 E_ERROR 유형 오류로 바뀌어 결국 PHP 코드 실행이 중지됩니다. PHP 7, 모든 치명적인 오류가 Error를 발생시키는 것은 아니며, 특정 상황에서 발생하는 일부 치명적인 오류(메모리 부족)로 인해 여전히 코드 실행이 중지됩니다. 또한 발생한 오류를 포착하여 처리하지 않으면 코드 실행이 계속 중지됩니다.
// 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 stringPHP 7의 오류는 예외를 상속하지 않습니다. 그 이유는 PHP 5의 예외를 포착하고 처리하는 코드가 이러한 오류를 포착하는 것을 방지하기 위한 것입니다. PHP 5에서는 이러한 치명적인 오류로 인해 코드 실행이 중단되기 때문입니다. 오류와 예외는 모두 Throwable에서 상속됩니다. PHP 7에서 Throwable은 인터페이스이며, throw 키워드를 통해 던져질 수 있는 모든 객체는 이 인터페이스를 구현합니다.
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 修复。
위 내용은 PHP7에서 오류를 처리하는 방법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!