[原创]PHP 异常错误处理
错误与异常
错误和异常 在PHP 中不一样的, 它们都表明代码出现问题, 且都能提供错误信息.
Points:
- 错误 出现的时机比异常早
- 错误 可以委托给全局错误处理器处理, 有些错误是无法恢复的, 会导致脚本停止
- 异常 要先实例化(Exception类), 然后抛出, 可以被捕获(try...catch)
- 异常 捕获后可以就地处理, 无需停止脚本(任何未被捕获的异常都会导致脚本停止)
异常的使用:
主动出击: 在遇到无法修复的状况时(当前上下文不知道如何处理)主动抛出, 交由使用者处理
eg. 数据库连接超时, 传入参数类型不符合条件等.
eg. 组件和框架的作者尤其无法确定如何处理异常状况, 通常会抛出异常, 交由具体使用者去处理.
被动防守: 预测潜在的问题, 减轻影响(将可能抛出异常的代码放在 try/catch 块中)
PHP 7 注意:
PHP 7中, 大多数错误被作为 Error异常 抛出, 能够被捕获.
若未被捕获且未注册异常处理函数(通过 set_exception_handler() 注册), 则会按照传统方式处理(指PHP7之前版本): 被报告为一个致命错误(Fatal Error), 可被 set_error_handler() 处理.
异常类
PHP 内置异常类:
SPL 扩充的异常类(均继承自 Exception 类):
错误类(PHP >= 7)
- Throwable
- Error
- ArithmeticError
- DivisionByZeroError
- AssertionError
- ParseError
- TypeError
- ArithmeticError
- Exception
- ...
- Error
注意: PHP 7 中, Error 和 Exception 都继承自 Throwable, 因此在捕获(try...catch)时可通过捕获 Throwable 来同时捕获异常和错误
try {
// do something
} catch (\Throwable $e) {
// log error or sth.
}
错误
php 能触发不同类型的错误:
- 致命错误
- 运行时错误
- 编译时错误
- 启动错误
- 用户触发错误(少见)
错误报告级别
error_reporting(int $level);
PHP 5.3 及以上, 默认的错误报告级别是
E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED(不会显示 E_NOTICE、 E_STRICT 、E_DEPRECATED)
部分错误级别解释:
| 错误级别 | 解释 | 建议 |
|---|---|---|
| E_NOTICE | 运行时通知。表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。 | 开发期间使用, 会对代码中可能出现的bug进行警告, 节省调试时间 |
| E_STRICT | 启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。 E_ALL 不包含 E_STRICT, 因此默认不激活 |
开发期间使用 |
| E_DEPRECATED | 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。 | 开启 |
错误报告设置
错误报告遵循原则
- 要报告错误
- 开发环境要显示错误
- 生产环境要不能显示错误(安全考虑)
- 开发环境和生产环境都要记录错误
注意分清 报告错误 和 显示错误 这两个概念的区别.
php.ini
开发环境推荐错误报告方式
;显示错误
display_startup_errors = On
display_errors = On
;报告错误
error_reporting = -1
;记录错误
log_errors = On
生产环境推荐错误报告方式
;显示错误
display_startup_errors = Off
display_errors = Off
;报告错误
error_reporting = E_ALL & ~E_NOTICE
;记录错误
log_errors = On
部分参数解释
| 参数 | 解释 | 建议 |
|---|---|---|
| display_errors | 设置是否将错误信息作为输出的一部分显示到屏幕,或者对用户隐藏而不显示 | 开发环境打开 生产环境务必关闭 |
| display_startup_errors | 即使 display_errors 设置为开启, PHP 启动过程中的错误信息也不会被显示。强烈建议除了调试目的以外,将 display_startup_errors 设置为关闭。 | 开发环境打开 生产环境务必关闭 |
| log_errors | 设置是否将脚本运行的错误信息记录到服务器错误日志或者error_log之中 | 打开 |
范例代码
<?php
// 关闭所有PHP错误报告
error_reporting(0);
// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);
// 报告 E_NOTICE也挺好 (报告未初始化的变量
// 或者捕获变量名的错误拼写)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
// 除了 E_NOTICE,报告其他所有错误
error_reporting(E_ALL ^ E_NOTICE);
// 报告所有 PHP 错误 (参见 changelog)
error_reporting(E_ALL);
// 报告所有 PHP 错误
error_reporting(-1);
// 和 error_reporting(E_ALL); 一样
ini_set('error_reporting', E_ALL);
?>
全局异常处理程序
捕获所有未被捕获的异常: 通过 set_exception_handler 注册全局异常处理程序.
函数
set_exception_handler ( callable $exception_handler ) : callable
// 注册异常处理程序
set_exception_handler('handleException');
// 重置异常处理程序为默认值
// set_exception_handler(null);
// 还原成之前的异常处理程序
// restore_exception_handler();
// < PHP 7
handleException(Exception $ex)
{
// 记录错误日志
echo "Uncaught exception: " , $ex->getMessage(), "\n";
// 开发环境显示调试信息(推荐 filp/whoops 扩展包)
// ...
// 生产环境显示对用户友好的页面(信息)
// ...
}
// >= PHP 7
// 大多数错误抛出 Error 异常, 也能被捕获, 因此参数类型必须是 Throwable, 否则会引起问题.
handleException(Throwable $ex)
{
// ...
}
在用户自定义异常处理函数内部, 可根据情况做一下处理:
- 日志记录错误
- web 渲染错误页面
- console 渲染错误提示
全局错误处理函数
通过设置全局错误处理程序, 使用自己的自定义方式拦截并处理PHP错误, 包括但不限于:
- 记录详细错误日志
- 对数据/文件做清理回收
- 转换成 ErrorException 对象, 再由处理异常的流程来处理错误.
注册全局错误处理程序
set_error_handler( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] ) : mixed
$error_types 指定的错误类型会被该错误处理函数拦截 ( 除非该函数返回了 false),不受 error_report() 影响.
处理程序
# 错误处理函数参数
# $errno 错误等级(对应 E_* 常量)
# $errstr 错误消息
# $errfile 发生错误的文件名
# $errline 发生错误的行号
# $errcontext 一个数组, 指向错误发生时可用的符号表(可选参数), 通常不用(php7.2后废弃)
function error_handler(int $errno, string $errstr, string $errfile, int $errline, array $errcontext) {
// 处理错误
}
带 @ 前缀的语句发生错误时, $errno 值为 0
脚本会在错误处理函数结束后从出错的地方继续执行 (因此必要时需主动调用
die()或exit()以结束脚本)如果错误发生在脚本执行之前(比如文件上传时),将不会 调用自定义的错误处理程序因为它尚未在那时注册。
如果函数返回 FALSE,标准错误处理处理程序将会继续调用。
无法捕获的错误类型
以下级别的错误不能由用户定义的错误处理函数来捕获:
| 错误级别 | 解释 |
|---|---|
| E_ERROR | 致命的运行错误, 一般不可恢复(eg. 内存分配导致的问题) |
| E_PARSE | 编译时语法解析错误, 由分析器产生 |
| E_CORE_ERROR | PHP初始化启动过程中发生的致命错误, 由php引擎核心产生 |
| E_CORE_WARNING | PHP初始化启动过程中发生的警告(非致命错误), 由php引擎核心产生 |
| E_COMPILE_ERROR | 致命编译时错误。类似E_ERROR, 但是是由Zend脚本引擎产生的。 |
| E_COMPILE_WARNING | 编译时警告 (非致命错误)。类似 E_WARNING,但是是由Zend脚本引擎产生的。 |
以及在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。
这些无法捕获的错误, 可在 register_shutdown_function() 中处理( 但脚本仍会结束 )
范例代码
function handleError($errno, $errstr, $errfile = '', $errline = 0)
{
if (!(error_reporting() & $errno)) {
// 错误类型未包含在 error_reporting() 里, 因此将它交由PHP标准错误处理程序来处理
return false;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
将 错误 转换为 异常
开发/生产环境处理错误和异常
开发环境
php 默认的错误信息很糟糕, 为了更高的帮助调试程序, 可以使用 filp/whoops 扩展包.
安装
composer require filp/whoops
使用(web)
$whoops = new \Whoops\Run;
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
$whoops->register();
如果php脚本触发PHP错误, 或应用没有捕获异常, 则开发人员就可以看到 Whoops 的图形化诊断页面.
可用的处理器
PrettyPageHandler- 显示美观的web页面显示错误信息PlainTextHandler- 用于命令行程序时输出纯文本信息CallbackHandler- 将闭包或其他可调用包装为处理程序JsonResponseHandler- 捕获异常, 并以 JSON 字符串形式返回, 比如用于 AJAX 请求XmlResponseHandler- 捕获异常, 但是是以XML格式字符串返回
生产环境
记录错误信息通常使用 error_log() 函数以将错误信息记录到文件系统或syslog.
error_log ( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]] ) : bool
message_type 参数, 设置错误应该发送到何处。可能的信息类型有以下几个:
0 message发送到 PHP 的系统日志,使用 操作系统的日志机制或者一个文件,取决于 error_log 指令设置了什么。 这是个默认的选项。1 message发送到参数destination设置的邮件地址。 第四个参数extra_headers只有在这个类型里才会被用到。2 不再是一个选项。 3 message被发送到位置为destination的文件里。 字符message不会默认被当做新的一行。4 message直接发送到 SAPI 的日志处理程序中。
一个更好的选择是使用 monolog/monolog 扩展包
<?php
require "vendor/autoload.php";
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
// add records to the log
$log->warning('Foo');
$log->error('Bar');
示例: 在生产环境中使用 Monolog 记录日志, 严重错误使用邮件通知
require "vendor/autoload.php";
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SwiftMailerHandler;
date_default_timezone_set('Asia/Shanghai');
// 设置monolog
$logger = new Logger('my-app-name');
$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
// 添加SwiftMailer Handler, 遇到严重错误时使用邮件通知
// Create the Transport
$transport = (new Swift_SmtpTransport('smtp.example.org', 25))
->setUsername('your username')
->setPassword('your password');
// Create the Mailer using your created Transport
$mailer = new Swift_Mailer($transport);
// Create a message
$message = (new Swift_Message('Wonderful Subject'))
->setFrom(['john@doe.com' => 'John Doe'])
->setTo(['receiver@domain.org', 'other@domain.org' => 'A name']);
$logger->pushHandler(new SwiftMailerHandler($mailer, $message, Logger::CRITICAL));
// 使用日志记录器
$logger->critical('The server is on fire!');
php中止时的回调函数
register_shutdown_function(function () {
// do sth...
}, $para1, $param2, ...)
注册一个 callback ,它会在脚本执行完成或者 exit() 后被调用。
Note:
可注册多个回调函数(不会互相覆盖, 依照注册顺序依次调用), 在php脚本中止时会被调用到.
如果在注册的方法内部调用 exit(), 那么所有处理会被中止,并且其他注册的中止回调也不会再被调用。
由于部分错误无法被 set_error_handler 捕获, 因此需配合 register_shutdown_function, 判断脚本退出的原因, 若是因为未被捕获的致命错误, 则需要处理(日志记录等)
register_shutdown_function('handleShutdown');
function handleShutdown()
{
// 如果是因为严重错误(未被捕获)导致脚本退出, 则需要处理(作为对 set_error_handler的补充)
if (! is_null($error = error_get_last()) && isFatal($error['type'])) {
// handleException() 函数同时处理 set_exception_handler
handleException(new \ErrorException(
$error['message'], $error['type'], 0, $error['file'], $error['line'],
));
}
}
function isFatal($type)
{
// 以下错误无法被 set_error_handler 捕获: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING
return in_array($type, [E_COMPILE_ERROR, E_CORE_ERROR, E_ERROR, E_PARSE]);
}
Note:
进程被信号 SIGTERM 或 SIGKILL 杀死时中止函数不会被调用. 可通过 pcntl_signal 捕获信号, 再在其中调用 exit() 来进行正常中止.
[原创]PHP 异常错误处理的更多相关文章
- [转]ORACLE 异常错误处理
本文转自:http://www.cnblogs.com/soundcode/archive/2012/01/10/2318385.html 本篇主要内容如下: 5.1 异常处理概念 5.1.1 预定义 ...
- [推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼、百战不殆)
原文:[推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼.百战不殆) [推荐]ORACLE PL/SQL编程之五: 异常错误处理(知已知彼.百战不殆) 继上三篇:ORACLE PL/S ...
- 出现java.lang.NoClassDefFoundError: com/google/common/base/Charsets异常错误
使用selenium,出现java.lang.NoClassDefFoundError: com/google/common/base/Charsets异常错误 原因:selenium-server- ...
- 总结Selenium自动化测试方法(六)常见的异常错误处理
六.常见的异常错误处理 NoSuchElementException: Message: Unable to locate element: {"method":"xpa ...
- Java异常错误的面试题及答案
1) Java中什么是Exception? 这个问题经常在第一次问有关异常的时候或者是面试菜鸟的时候问.我从来没见过面高级或者资深工程师的 时候有人问这玩意,但是对于菜鸟,是很愿意问这个的.简单来说, ...
- 总结:整理 oracle异常错误处理 .
5.1 异常处理概念 5.1.1 预定义的异常处理 5.1.2 非预定义的异常处理 5.1.3 用户自定义的异常处理 5.1.4 用户定义的异常处理 5.2 异常错误传播 5.2.1 在执行部分引发 ...
- 解决Python2.7的UnicodeEncodeError:'ascii' codec can't encode characters in position 0-78: ordinal not in range(128)异常错误
解决Python2.7的UnicodeEncodeError: 'ascii' codec can't encode异常错误 大家都知道,在使用python进行网络爬虫时,最头疼的就是转码问题,下面是 ...
- oracle有三种类型的异常错误: 预定义 ( Predefined )错误里面的常见错误
oracle有三种类型的异常错误: 预定义 ( Predefined )错误, 非预定义 ( Predefined )错误, 用户定义(User_define) 错误 预定义 ( Predefined ...
- 整理 oracle异常错误处理
5.1 异常处理概念 5.1.1 预定义的异常处理 5.1.2 非预定义的异常处理 5.1.3 用户自定义的异常处理 5.1.4 用户定义的异常处理 5.2 异常错误传播 5.2.1 在执行部分引发 ...
随机推荐
- mysql数据库优化总结 有图 有用
对于一个以数据为中心的应用,数据库的好坏直接影响到程序的性能,因此数据库性能至关重要.一般来说,要保证数据库的效率,要做好以下四个方面的工作:数据库设计.sql语句优化.数据库参数配置.恰当的硬件资源 ...
- [SoapUI] SoapUI命令行方式运行
https://www.soapui.org/test-automation/running-from-command-line/functional-tests.html TestRunner Co ...
- exp,expdb,imp,impdb的使用
1.使用expdp要先在数据库中创建directory,并给相应的用户read,write权限. SQL>create dexp和empdp的区别irectory dmpdir as ‘/u01 ...
- mysql:unknown variable 'default -collation=utf8_general_ci'
无法登陆,因为在配置文档中设置了默认编码方式 将它注释掉,问题就解决了 在utf8_bin中你就找不到 txt = 'A' 的那一行, 而 utf8_general_ci 则可以.utf8_gener ...
- setoolkit基础
1.社会工程学攻击 2.快速追踪测试 3.第三方模块 4.升级软件 5.升级配置 6.帮助 99.退出 1.鱼叉式网络钓鱼攻击 2.网页攻击 3.传染媒介式(俗称木马) 4.建立payloaad和li ...
- Mac上phantomjs装了不能用的解决
下了,装了.然后整这句命令: phantomjs --version 直接报错.. 查了半天的官方文档,居然连个毛线都么找到.终于...墨迹到了一个方法,就抱着试一试的心态在terminal里输入了这 ...
- 编写高质量代码改善C#程序的157个建议——建议95:避免在构造方法中调用虚成员
建议95:避免在构造方法中调用虚成员 在构造方法中调用虚方法会带来一些意想不到的错误,虽然这种方法不常见,但还是需要注意这类陷阱. static void Main() { American amer ...
- strncmp用法说明
函数原型 int strcmp(char *str1,char * str2,int n) 功能 比较字符串str1和str2的前n个字符. 头文件 #include <string.h> ...
- PreTranslateMessage()函数捕获键盘按键消息
01. PreTranslateMessage函数,常用于屏蔽MFC对话框中默认的Enter和ESC消息 函数原型:BOOL PreTranslateMessage(MSG* pMsg) 用法举例: ...
- 20145218张晓涵_Web基础
20145218张晓涵_Web基础 基础知识 Apache一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,由于其多平台和安全性被广泛使用,是最流行的Web服务器端软件之一.它快速.可靠并且 ...