PHP(PHP_VERSION >= 7) 的 Error / Exception 的捕获与处理仍是值得一说的,优雅处理错误与异常,在提高框架友好度的同时,也提高了开发效率。php
# 系统级用户代码的一些错误类型 可由 try ... catch ... 捕获 E_PARSE 解析时错误 语法解析错误 少个分号 多个逗号一类的 致命错误 E_ERROR 运行时错误 好比调用了未定义的函数或方法 致命错误 # 系统级用户代码的一些错误类型 可由 set_error_handler 捕获处理 E_WARNING 运行时警告 调用了未定义的变量 E_NOTICE 运行时提醒 E_DEPRECATED 运行时已废弃的函数或方法 # 用户级自定义错误 可由 trigger_error 触发 可由 set_error_handler 捕获处理 E_USER_ERROR 用户自定义错误 致命错误 未处理也会致使程序退出 E_USER_WARNING E_USER_NOTICE E_USER_DEPRECATED ==========================开发中常遇到/不常遇到分割线======================= # Zend Engine 内部的一些错误 应该也能经过 try ... catch ... 捕获 略难测试 E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING #编码标准化警告(建议如何修改以向前兼容) E_STRICT 部分 try ... catch ... 部分 set_error_handler E_RECOVERABLE_ERROR
以上为 PHP 的一些错误监听级别,经常使用于 error_reporting 和 set_error_handler 的监听级别设定。浏览器
<?php error_reporting(E_ALL & ~E_NOTICE); set_error_handler(function handler($error_no, $error_msg, $error_file, $error_line) { }, E_ALL | E_STRICT);
PHP 的错误处理其实能够分为:用户自定义错误处理 和 PHP标准错误处理,二者的关系至关于两层错误捕捉器,系统会先检测是否认义了 用户自定义错误处理,不然会将错误交由 PHP标准错误处理 进行处理。架构
注意:PHP 的全部的 Exception 都属于 E_ERROR 级的错误,抛出时若是没有被捕获而交由 PHP 标准错误处理的话,就会 Fatal Error 致使程序退出执行。固然,PHP7 为了细化错误级别,划分了 Error 级 Error 的衍生类,这些也都属于 E_ERROR 级别的错误。框架
PHP 标准错误处理是在一些错误没有被用户捕获处理(没有被 try ... catch ... 或 set_error_handler 捕获处理)时,错误 会递交至 PHP 标准错误处理。相关的设置项以下:函数
<?php // 监听捕获的错误级别 error_reporting(E_ALL); // 是否开启错误信息回显 将错误输出至标准输出(浏览器/命令行) ini_set('display_errors', true); // 死否开启错误日志记录 将错误记录至 ini:error_log 指定文件 ini_set('log_errors', true); ini_set('error_log', __DIR__ . '/php-errors.log');
获取或设定当前错误的监听级别。要注意,是获取或设定的 PHP 标准错误处理 的级别,不会有效于 try...catch... 或 set_error_handler。测试
是否将错误信息回显至标准输出。默认开启,生产环境下强烈建议 关闭 此项。ui
是否记录错误日志。默认关闭,生产环境下强烈建议 开启 此项。编码
错误日志的保存文件。注意:若是路径无效,display_errors 会被强制开启。spa
set_error_handler 并不是能够捕获全部错误,且 set_error_handler 不会终止程序继续执行。处理后若返回 false,则错误会被继续递交给 PHP 标准错误处理 流程。.net
能够捕获: E_WARNING & E_NOTICE & E_DEPRCATED & E_USER_* & 部分 E_STRICT 级的错误。
没法捕获: E_ERROR & E_PARSE & E_CORE_* & E_COMPLIE_* 级的错误。
有自身的错误捕获级别,默认E_ALL | E_STRICT,且不受 error_reporting 设定的级别的影响。这里要理解,用户自定义错误处理 和 PHP 标准错误处理 是两层错误捕捉器,有独立的捕获级别。
<?php // 用户自定义错误处理 set_error_handler(function ($error_no, $error_msg, $error_file, $error_line) { switch ($error_no) { case E_WARNING: $level_tips = 'PHP Warning: '; break; case E_NOTICE: $level_tips = 'PHP Notice: '; break; case E_DEPRECATED: $level_tips = 'PHP Deprecated: '; break; case E_USER_ERROR: $level_tips = 'User Error: '; break; case E_USER_WARNING: $level_tips = 'User Warning: '; break; case E_USER_NOTICE: $level_tips = 'User Notice: '; break; case E_USER_DEPRECATED: $level_tips = 'User Deprecated: '; break; case E_STRICT: $level_tips = 'PHP Strict: '; break; default: $level_tips = 'Unkonw Type Error: '; break; } // do some handle $error = $level_tips . $error_msg . ' in ' . $error_file . ' on ' . $error_line; echo $error . PHP_EOL; // 若是 return false 则错误会继续递交给 PHP 标准错误处理 // return false; }, E_ALL | E_STRICT); trigger_error("用户自定义 notice error"); trigger_error("用户自定义 warning error", E_USER_WARNING); trigger_error("用户自定义 deprecated error", E_USER_DEPRECATED); trigger_error("用户自定义 fatal error", E_USER_ERROR);
trigger_error 用来触发用户级别的自定义错误,可使用 set_error_handler 捕获处理。
默认的错误级别是 E_USER_NOTICE,咱们能够自定义。
这里须要注意的是:E_USER_ERROR 级别的错误若是被 PHP 标准错误处理 捕获,脚本也会退出执行错误。
<?php error_reporting(E_ALL); ini_set('display_errors', true); trigger_error('用户自定义 notice error', E_USER_NOTICE); echo 'continue A' . PHP_EOL; trigger_error('用户自定义 warning error', E_USER_WARNING); echo 'continue B' . PHP_EOL; trigger_error('用户自定义 deprecated error', E_USER_DEPRECATED); echo 'continue C' . PHP_EOL; trigger_error('用户自定义 fatal error', E_USER_ERROR); echo 'D point will not be executed' . PHP_EOL;
set_exception_handler 用户自定义捕获异常 handler,异常没有被 try ... catch 捕获处理的话会被抛出,此时系统会检查上下文是否注册了 set_exception_handler。
若是未注册 则进入 PHP 标准错误处理 致命错误退出执行。
若是已注册 则进入 set_exception_handler 处理 程序依然会退出执行。
而 try ... catch ... 捕获异常后仍不会退出执行,故强烈建议将有异常的执行逻辑放入 try ... catch 中
<?php // 捕获异常后程序会退出执行 set_exception_handler(function ($exception) { echo $exception; // 此处程序会退出执行 异常到此结束 并不会交给 PHP 标准异常处理 }); throw new Exception('hello world!'); echo 'will i be executed?';
开发中用户层面的 set_error_hanlder 没法捕获的错误还剩下 E_ERROR 和 E_PARSE 两个级别,使用 try ... catch ... 则能够将这俩货捕捉到。
来个小插曲:你们对 E_PARSE 熟悉又陌生,可能常常遇到(各大框架都有此级别错误捕获提示),但本身不知道如何捕获,其实首先要理解 E_PARSE 错误的发生时段。
E_PARSE:即语法解析错误,Syntax Error then Parse Error,PHP 将脚本载入 Zend Engine 后,最开始要作的就是检查基本语法是否有误,无误才会调用解释器,一行行的开始解释运行。
这里就有个鸡生蛋,蛋生鸡的问题了。以下代码:
<?php //认为关闭了错误捕获 不该该有错误报出才对 error_reporting(0); echo 'i lose semicolon'而后不少人会诧异,明明关闭了错误提示,为何还会报错?
没错,代码的确正确的书写了关闭错误提示的意图。但还没被执行到时,脚本就由于最初始的语法解析错误,被 Zend Engine 抛出 Parse Error 终止执行了。同时还要理解,PHP include / require 只有在真的解释到这一行代码时,引用的文件才会被载入--解析--解释执行。
因此,咱们须要有一个无错的 try ... catch ... 容器,在容器中即可以对后续引用的外部脚本进行 E_PARSE 错误捕捉。
例如框架自身是一个无错的运行容器,开发者自写的 MVC 是被 include / require 到此容器中 解析 -- 解释执行 的,用户代码的语法错误即会被容器的 try ... catch ... 优雅的捕获到。
try ... catch ... 的错误捕获级别一样不受 error_reporting 影响,咱们能够经过 多层 catch 细化各种型的错误。
<?php // typeError demo function foo(): int { return 'hello world'; } try { //foo(); // echo strlen('hello world', 233); } catch (\ErrorException $errorException) { // 捕获错误异常 echo 'ErrorException: ' . $errorException . PHP_EOL; } catch (\Exception $exception) { // 捕获异常 echo 'Exception: ' . $exception . PHP_EOL; } catch (\TypeError $typeError) { // 捕获类型错误 返回值/参数不正确 echo 'Type Error: ' . $typeError . PHP_EOL; } catch (\ParseError $parseError) { // 捕获解析错误 语法错误 echo 'Parse Error: ' . $parseError . PHP_EOL; } catch (\DivisionByZeroError $divisionByZeroError) { // 除 0 没法捕获 但 除 0 取余能够捕获 = = 很无奈 echo 'Division By Zero Error: ' . $divisionByZeroError . PHP_EOL; } catch (\Error $error) { // 基本错误 echo 'Error: ' . $error . PHP_EOL; }
这里要注意的是,DivisionByZeroError 在 PHP7 中依然没法隐式的完美捕获。准确的说:
当 x / 0 时抛出一个 E_WARNING 级别的错误,咱们能够 set_error_handler 捕获,而后再判断错误为
'Devision by zero' 时抛出一个 ErrorException 的异常交由 try ... catch ... 捕获处理便可。当 x % 0 时才会直接抛出 DivisionByZeroError 的错误。
固然,你也能够显示的判断除数为 0 来决定是否抛出个 DivisionByZeroError
<?php try { $divisor = 0; if ($divisor == 0) { throw new DivisionByZeroError('Division by zero!'); } echo 233 / $divisor; } catch (\DivisionByZeroError $error) { echo $error; }
Predefined Exceptions 预约义异常 可由系统自动抛出
Exception
ErrorException
Error
ArgumentCountError
ArithmeticError
AssertionError
DivisionByZeroError
ParseError
TypeError
SPL Exceptions SPL 标准规范异常 可供开发者规范代码自行抛出
BadFunctionCallException
BadMethodCallException
DomainException
InvalidArgumentException
LengthException
LogicException
OutOfBoundsException
OutOfRangeException
OverflowException
RangeException
RuntimeException
UnderflowException
UnexpectedValueException
下面的代码基本呈现和捕获了 PHP7 提供的全部预约义错误和异常,PHP 标准错误处理
<?php // php >= 7 // PHP 标准错误处理 捕获级别 error_reporting(E_ALL); // 是否将 标准错误处理 捕获的错误回显在 stdout 上 ini_set('display_errors', false); // 开启错误日志 ini_set('log_errors', true); // 若是错误日志路径无效 display_errors 依然会强制打开 ini_set('error_log', __DIR__ . '/php-errors.log'); /** * set_error_handler 用户自定义错误 handler * 可以捕获 E_WARNING E_NOTICE E_DEPRECATED E_USER_* E_STRICT 级的错误 * 没法捕获 E_ERROR E_PARSE E_CORE_* E_COMPILE_* [DivisionByZeroError TypeError] 级的错误 */ set_error_handler(function ($error_no, $error_msg, $error_file, $error_line) { switch ($error_no) { case E_WARNING: // x / 0 错误 PHP7 依然不能很友好的自动捕获 只会产生 E_WARNING 级的错误 // 捕获判断后 throw new DivisionByZeroError($error_msg) // 或者使用 intdiv(x, 0) 方法 会自动抛出 DivisionByZeroError 的错误 if (strcmp('Division by zero', $error_msg) == 0) { throw new \DivisionByZeroError($error_msg); } $level_tips = 'PHP Warning: '; break; case E_NOTICE: $level_tips = 'PHP Notice: '; break; case E_DEPRECATED: $level_tips = 'PHP Deprecated: '; break; case E_USER_ERROR: $level_tips = 'User Error: '; break; case E_USER_WARNING: $level_tips = 'User Warning: '; break; case E_USER_NOTICE: $level_tips = 'User Notice: '; break; case E_USER_DEPRECATED: $level_tips = 'User Deprecated: '; break; case E_STRICT: $level_tips = 'PHP Strict: '; break; default: $level_tips = 'Unkonw Type Error: '; break; } // do some handle $error = $level_tips . $error_msg . ' in ' . $error_file . ' on ' . $error_line; echo $error . PHP_EOL; // 若是 return false 则错误会继续递交给 PHP 标准错误处理 // return false; }, E_ALL | E_STRICT); /** * set_exception_handler 用户自定义捕获异常 handler * 异常没有被 try ... catch ... 捕获处理的话会被抛出 * 此时系统会检查上下文是否注册了 set_exception_handler * 若是未注册 则进入 PHP 标准异常处理 致命错误退出执行 * 若是已注册 则进入 set_exception_handler 处理 程序依然会退出执行 * 而 try ... catch ... 捕获异常后仍不会退出执行 * 故强烈建议将有异常的执行逻辑放入 try ... catch 中 */ set_exception_handler(function ($exception) { echo $exception; // 此处程序会退出执行 异常到此结束 并不会交给 PHP 标准异常处理 }); // type error demo function foo(): int { return 'result type error'; } // 捕获 E_ERROR E_PARSE 级的 Error // 捕获 Exception try { // 加载外部文件的正确写法 $file = __DIR__ . '/bar.inc.php'; if (file_exists($file)) { require_once $file; } else { throw new Exception($file . ' not exists!'); } // ParseError 解析错误 // bar.inc.php 的内容要有基本的语法错误: <?php echo 'some syntax error' // ArgumentCountError extends TypeError PHP >= 7.1.0 // strlen 参数错误 echo strlen('hello world', 4); // TypeError 类型错误 // foo 要求的返回类型为 int 但 return 了 string 类型错误 foo(); // DivisionByZeroError extends ArithmeticError // x / 0 会抛出 E_WARNING 的异常 但不会自动抛出 DivisionByZeroError // 咱们可使用 set_error_handler 进行捕获而后手动抛出 DivisionByZeroError 1 / 0; // Integer Divison 等同于 1 / 0 能够直接抛出 DivisionByZeroError intdiv(1, 0); // 除 0 取余 能够直接抛出 DivisionByZeroError 1 % 0; // ArithmeticError 错误 intdiv(PHP_INT_MIN, -1); // AssertionError 断言错误 assert('1 != 1'); // 调用未定义的函数 错误级别:E_ERROR bar(); } catch (\ErrorException $errorException) { // 错误异常 echo 'ErrorException: ' . $errorException . PHP_EOL; } catch (\Exception $exception) { // 异常 echo 'Exception: ' . $exception . PHP_EOL; } catch (\ParseError $parseError) { // 解析错误 语法错误 echo 'Parse Error: ' . $parseError . PHP_EOL; } catch (\ArgumentCountError $argumentCountError ) { // 传参非法错误 php >= 7.1.0 echo 'Argument Count Error: ' . $argumentCountError . PHP_EOL; } catch (\TypeError $typeError) { // 类型错误 返回值 echo 'Type Error: ' . $typeError . PHP_EOL; } catch (\DivisionByZeroError $divisionByZeroError) { // x / 0 不抛出 x % 0 能够抛出 // x / 0 能够用 intdiv(x, 0) 代替 会抛出 echo 'Division By Zero Error: ' . $divisionByZeroError . PHP_EOL; } catch (\ArithmeticError $arithmeticError) { // 算数运算错误 intdiv(PHP_INT_MIN, -1) 触发 echo 'Arithmetic Error: ' . $arithmeticError . PHP_EOL; } catch (\AssertionError $assertionError) { // 断言错误 echo 'Assertion Error: ' . $assertionError . PHP_EOL; } catch (\Error $error) { // 基本错误 echo 'Error: ' . $error . PHP_EOL; } echo "run finished!" . PHP_EOL;
一、PHP 容许用户自定义 Error 和 Exception 的捕获与处理。如用户未捕获处理,则会递交给 PHP 标准错/异常处理,根据 errror_reporting display_errors log_erros error_log 参数决定处理方式。生产环境应关闭 display_errors 同时开启 log_errors 记录错误日志。
二、set_error_handler 能够捕获 E_WARNING & E_NOTICE & E_DEPRECATED & E_USER_* 和 部分 E_STRICT 级的错误。set_error_handler 若是返回了 false 错误会递交给 PHP 标准错误处理。set_error_handler 不会终止程序执行。
三、trigger_error 能够用来抛出用户级的错误,且 E_USER_ERROR 效用等同于 E_ERROR,PHP 标准错误处理 捕获此级别的错误时会终止程序执行。
四、set_exception_handler 用户自定义异常捕获,捕获后程序依然会终止运行,但不会再将异常递交给 PHP 标准异常处理。
五、try ... catch 能够捕获全部的 Exception 和 E_ERROR & E_PARSE 级的错误。程序不会退出执行。
六、PHP 自带了一些 Predefined Exceptions,同时有规范一些 SPL Exceptions,供开发者规范本身的错误异常架构。