[TOC]
PHP错误和异常处理
PHP的错误和异常是两个概念
PHP的错误处理:
1.语法错误
2.环境错误
3.逻辑错误
PHP的异常类型:
PHP7的错误和异常
PHP 7 改变了大多数错误的报告方式。
不同于传统(PHP 5)的错误报告机制,现在大多数错误被作为Error异常抛出。
这种 Error 异常可以像Exception异常一样被第一个匹配的try/catch块所捕获。
如果没有匹配的catch块,则调用异常处理函数(事先通过set_exception_handler()注册)进行处理。
如果尚未注册异常处理函数,则按照传统方式处理:被报告为一个致命错误(Fatal Error)。
Error类并非继承自Exception类,所以不能用catch (Exception $e) { ... }来捕获Error。
你可以用catch (Error $e) { ... },或者通过注册异常处理函数(set_exception_handler())来捕获Error。
$string = 1;
try {
//未定义此对象
$string->abc();
} catch (Exception $e) {
echo "process exception";
} catch (Error $e) {
echo "process error";
}
PHP7 中出现了 Throwable 接口,该接口由 Error 和 Exception 实现,用户不能直接实现 Throwable 接口,而只能通过继承 Exception 来实现接口
可以直接这么写
// Code that may throw an Exception or Error.
$string = 1;
try {
//未定义此对象
$string->abc();
}catch (Throwable $t){
echo "process error";
}
PHP的错误
- 常见错误
常见错误:
常见的有16中,这里只说常用的.
PHP.ini的错误模块Error handing and logging中有介绍
Deprecated 最低级的错误,表示为不推荐的操作
代码继续执行
比如调用一些已启用的预定义变量时
Notice 通知级别错误
代码继续执行
比如调用未声明的变量,比如数组下标没有引号,又没有这个常量
会暴出一个,没有这个常量.
Waring 警告级别错误
代码继续执行
语法出现不恰当的情况,参数少几个,或者参数类型有误
Fatal error 致命级别的错误
代码终止执行
比如调用不存在的函数
parse error 语法解析错误 最高级别错误
代码中止执行
代码还没有执行,进行语法扫描时的错误.
如果出现该错误,其他的错误都不会显示了.
E_USER_ 相关的错误 用户定义错误
如 E_user的warning E_user的error等等
手动抛出异常的时候用.
错误对照表
数字 常量 说明
1 E_ERROR 致命错误,脚本执行中断,就是脚本中有不可识别的东西出现
举例: Error:Invalid parameters. Invalid parameter name
2 E_WARNING 部分代码出错,但不影响整体运行
举例: Warning: require_once(E:/include/config_base.php)
4 E_PARSE 字符、变量或结束的地方写规范有误
举例: Parse error: syntax error, unexpected $end in
8 E_NOTICE 一般通知,如变量未定义等
举例: Notice: Undefined variable: p in E:\web\index.php on line 17
16 E_CORE_ERROR PHP进程在启动时,发生了致命性错误
举例: 暂无
32 E_CORE_WARNING 在PHP启动时警告(非致命性错误)
举例: 暂无
64 E_COMPILE_ERROR 编译时致命性错误
举例: 暂无
128 E_COMPILE_WARNING 编译时警告级错误
举例: 暂无
256 E_USER_ERROR 用户自定义的错误消息
举例: 暂无
512 E_USER_WARNING 用户自定义的警告消息
举例: 暂无
1024 E_USER_NOTICE 用户自定义的提醒消息
举例: 暂无
2047 E_ALL 以上所有的报错信息,但不包括E_STRICT的报错信息
举例: 暂无
2048 E_STRICT 编码标准化警告,允许PHP建议如何修改代码以确保最佳的互操作性向前兼容性。
- PHP 配置文件中与错误相关选项
常用错误配置:
通过PHP的配置文件来设置
# 设置错误报告级别
error_reporting
配置文件中有详细描述,可以结合使用也可以单独使用
error_reporting = E_ALL&~E_NOTICE
显示所有错误, 并且除了notice错误
& 表示并且
| 表示或者
~ 表示除了
^ 表示除了
E_ALL 所有的错误和警告
E_NOTICE,运行时的提醒。基本无大碍可以正常使用。
E_WARNING,运行时的警告,部分功能失效,脚本继续执行
E_ERROR,致命的错误,阻止脚本运行(快死了,脑袋没了)
# 是否以内嵌形式显示错误
display_errors
display_errors = On/Off 显示/关闭
此处对解析错误无效,因为他属于语法检测阶段.
# 是否记录错误日志到文件中
log_errors
log_errors = On/Off 显示/关闭
# 指定错误输出到文件的路径
error_log
error_log = /var/log/php/php.log
error_log = syslog设置与系统日志保存在一起
指定到系统日志中需要openlog()之类的操作,具体不写了
我从来都不用
# 设置记录日志的文件最大的大小
log_errors_max_len
# 是否忽略重复的错误消息
ignore_repeated_errors
# 是否忽略重复的来源的错误消息
ignore_repeated_source
# 最后一个错误消息将永远保存在$php_errormsg的
# 预定义变量中
track_errors
通过PHP代码动态设置
函数来设置错误级别,不传值的话,默认得到当前错误级别的位掩码
error_reporting()
屏蔽所有错误,解析错误无效
error_reporting(0)
显示所有错误
error_reporting(-1)
显示所有的错误信息,并且不显示notice错误
error_reporting('E_ALL&~E_NOTICE');
运行时设置配置选项的值
init_set()
屏蔽所有错误,解析错误无效
init_set('error_reporting',0)
显示所有错误
init_set('error_reporting',-1)
隐藏错误信息
init_set('display_errors',0)
- 设置错误的级别
触发错误的功能, 不仅咸鱼PHP的解析器.
我们也可以手动触发错误
trigger_error('要抛出的提示语句',E_USER_NOTICE);
是user_error()的别名
trigger_error('数据类型有误',E_USER_NOTICE);
trigger_error('数据类型有误',E_USER_WARNING);
trigger_error('数据类型有误',E_USER_ERROR);
- 记录错误 发送错误
# 将错误记录到指定的文件中
log_errors = On
error_log = /var/log/php/php.error.log
这时候如果上线了记得关掉错误显示
display_errors = Off
error_reporting = E_ALL
或者
ini_set('display_errors','off');
error_reporting(-1);
ini_set('log_error','on');
ini_set('error_log','/var/log/php/php.error.log');
附加设置:
# 显示重复的错误消息
ini_set('ignore_repeated_errors','on');
# 显示重复来源的错误消息
ini_set('ignore_repeated_source','on');
# 即将错误信息已邮件的形式发送
error_log($num);
$num = 0; 发送到 PHP 的系统日志
$num = 1;发送到参数 destination 设置的邮件地址
.........等等
首先需要配置好PHP的mail或者sendmail
error_log('程序崩溃',1,'邮箱地址');
设置自定义错误处理器
- 接管系统错误处理set_error_handler();
设置一个用户定义的错误处理函数,通过该函数进行自定义错误的处理
set_error_handler( callable $error_handler [, int $error_types = E_ALL | E_STRICT ])
//直接输入函数名进行调用
set_error_handler('deal');
//数组形式, 第一个为类型, 第二个为方法名.
set_error_handler(['myerrorhandler', 'deal']);
$error_handler
所要调用的回调函数
回调函数需要的参数:
errno 错误级别即错误号
errstr 包含了错误的信息
[errfile] 包含错误的文件名
[errline] 包含了错误发生的行号
errcontext 是一个指向错误发生时活动符号表的array.PHP7.2后被弃用了
$error_types
指定错误级别
就像error_reporting的ini设置能够控制错误显示一样.
此参数能够用于屏蔽 error_handler 的触发。
如果没有该掩码, 无论 error_reporting 是如何设置的,
error_handler 都会在每个错误发生时被调用。
取消自定义错误处理的接管
restore_error_handler();
该函数可以关闭set_error_handler()设置的自定义
但是在实际使用当中, 我发现他的作用是恢复到上一次的设定.
比如设置了两次set_error_handler();
调用一次restore_error_handler();只会恢复到第一次set_error_handler.
调用第二次restore才会还原到为set_error_heandler状态.
/**
会绕过PHP标准的错误处理程序,除非回调函数返回了false.
error_reporting()设置将不会起到作用,而错误处理函数将继续执行
不能被用户自定义函数处理的错误:
E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,
和在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。
如果错误发生在脚本执行之前(比如文件上传时),将不会 调用自定义的错误处理程序因为它尚未在那时注册
*/
//我的简单示例
/**
* Method customError
* @desc 自定义错误处理函数
*
* @author LiuHao <lh@btctrade.com>
* @param string $errno 错误号
* @param string $errmsg 错误信息
* @param string $file 错误的文件路径
* @param string $line 错误所在行号
*
* @return void
*/
function customError($errno, $errmsg, $file, $line)
{
echo "错误代码: {$errno}{$errmsg}\n" . PHP_EOL;
echo "错误行号: {$file}文件中的第{$line}行\n" . PHP_EOL;
echo "PHP版本: " . PHP_VERSION . "(" . PHP_OS . ")" . PHP_EOL;
//如果要停止进程请使用exit
exit();
}
//设置调用的函数
set_error_handler('customError');
//不可被自定义错误处理, 捕捉的错误
func(); //致命错误
$a //语法错误
- 设置自定义错误处理器
//代码示例
<?php
/**
* @Author: LiuHao
* @Date: 2018/2/10 下午3:26
* @Email: lh@btctrade.com
* @File: myerrorhandler.php
* @Desc: 自定义错误处理器
*/
class myerrorhandler
{
public $message = '';
public $filename = '';
public $line = 0;
public $vars = [];
/**
* myerrorhandler constructor.
* @param string $message 消息
* @param string $filename 文件名
* @param string $line 行号
* @param array $vars 额外信息
* @author liuhao <lh@btctrade.com>
*/
public function __construct($message, $filename, $line, $vars)
{
$this->message = $message;
$this->filename = $filename;
$this->line = $line;
$this->vars = $vars;
}
/**
* Method deal
* @desc ......
*
* @author LiuHao <lh@btctrade.com>
* @static
* @param $errno
* @param $errmsg
* @param $filename
* @param $line
* @param $vars
*
* @return void
*/
public static function deal($errno, $errmsg, $filename, $line, $vars)
{
$self = new self($errno, $errmsg, $filename, $line, $vars);
//根据不同的错误号来选择处理方式
switch ($errno) {
case E_USER_ERROR:
return $self->dealError();
break;
case E_WARNING:
case E_USER_WARNING:
return $self->dealWaring();
break;
case E_NOTICE:
case E_USER_NOTICE:
return $self->dealNotice();
break;
default:
return false;
}
}
public function dealError()
{
//开启内存缓冲, 防止内容输出
ob_start();
//返回一条debug追踪回溯
debug_print_backtrace();
//得到内存缓冲中的内容
$backtrace = ob_get_flush();
$errMsg = <<<EOF
出现了致命错误,如下:
产生错误的文件:{$this->filename}
产生错误的信息:{$this->message}
产生错误的行号:{$this->line}
追踪信息:{$backtrace}
EOF;
exit($errMsg);
}
public function dealWaring()
{
$dateTime = date('Y-m-d H:i:s', time());
$errMsg = <<<EOF
出现了警告错误,如下:
产生警告的文件:{$this->filename}
产生警告的信息:{$this->message}
产生警告的行号:{$this->line}
产生通知的时间:{$dateTime}
EOF;
self::log($errMsg);
}
public function dealNotice()
{
$dateTime = date('Y-m-d H:i:s', time());
$errMsg = <<<EOF
出现了通知错误,如下:\n
产生通知的文件:{$this->filename} -
产生通知的信息:{$this->message} -
产生通知的行号:{$this->line} -
产生通知的时间:{$dateTime} -
EOF;
self::log($errMsg);
}
/**
* Method log
* @desc ......
*
* @author LiuHao <lh@btctrade.com>
* @param $msg
* @param string $path
* @param string $file
*
* @return void
*/
public function log($msg, $path = './log', $file = '/error.log')
{
$filePath = $path . $file;
//文件信息不存在, 创建
if (!is_dir($path)) {
mkdir($path, '0777', true);
}
if (!file_exists($filePath)) {
touch($filePath);
}
//打开文件资源,追加错误信息
$handler = fopen($filePath, 'a+');
fwrite($handler, $msg);
fclose($handler);
}
}
//打开所有错误信息的捕捉
error_reporting(-1);
//展示所有错误信息
ini_set('display_errors',0);
//设置自定义错误捕捉
set_error_handler(['myerrorhandler', 'deal']);
//notice错误
echo $a;
//致命错误, 不可捕捉
test();
//语法错误,不可捕捉
$sys
当进程被关闭时被调用的函数-可以用来捕捉致命错误
进程停止调用函数
退出进程调用的函数
<?php
/**
* @Author: LiuHao
* @Date: 2018/2/11 下午11:39
* @Email: lh@btctrade.com
* @File: shutdown.php
* @Desc: ...
*/
// 设置一个当知心给关闭时可以被调用的函数
// 页面被强制停止
// 程序代码意外停止或者超时
//register_shutdown_function();
class shutdown
{
public function enScript()
{
if (error_get_last()) {
//最后一次错误的数组
var_dump(error_get_last());
}
//必须为绝对路径,
//因为register_shutdown_ 是从内存中进行调用的.
// 也就就是说PHP运行完之后调用的.
$path = '/Users/liuhao/www/demo/project_demo/error/log/shutdown.log';
file_put_contents($path, 'this is test');
exit(1);
}
}
//数组形式, 类名,方法名, 传参 , 注意非静态方法的类名需要new一下, 静态方法直接输入类名
//register_shutdown_function([ new shutdown(),'enScript']);
PHP中的异常处理
- 错误和异常的区别
/**
* PHP中的异常处理
*
* 异常和错误的区别是什么
*
* 异常:
* 程序运行和正常状况不同时, 我们认为出现异常.
* 使用throw抛出异常, 然后使用catch 来捕获异常.
* 每一个catch对应一个try, 可以使用多个catch也可以在try内嵌套多个try catch
* 如果所有的try catch都没有命中, 代码就会继续执行
*/
//如:
/**
* try {
* //代码段:
* //0不能作为除数
* $num = 3 / 0;
* var_dump($num);
*
* } catch (Exception $e) {
* echo $e->getMessage();
* }
*/
//这样写实没有走到catch段中的, 代码会继续执行
//PHP代码首先触发的本身的错误, 不会手动触发异常,不会自动走到catch中来抛出异常
//需要手动throw来抛出一个异常
//如果在try代码块中出现错误, 需要使用throw来抛出一个异常,这时候在catch中才能够捕捉的到异常.
//throw 后的代码不会再运继续运行
//catch捕获到异常后程序会继续执行, 如果想要退出,可以使用exit来退出进程
//部分PHP的内置异常类时可以自行抛出的如PDO
$num = null;
try {
//代码段:
//0不能作为除数
$num = 3 / 0;
var_dump($num);
} catch (Exception $e) {
echo $e->getMessage();
}
echo 'continue';
//手动抛出异常,然后使用catch来捕获
try {
$num1 = 3;
$num2 = 0;
if ($num2 == 0) {
throw new Exception('0不能作为除数', '333');
} else {
$res = $num1 / $num2;
}
} catch (Exception $e) {
echo 'Msg: ' . $e->getMessage();
echo '<hr/>';
echo 'Code: ' . $e->getCode();
echo '<hr/>';
echo 'file: ' . $e->getFile();
echo '<hr/>';
echo 'line: ' . $e->getLine();
echo '<hr/>';
}
//PDO异常自行抛出, 该案例就没就行手动抛出异常, 而是自行抛出的
try {
//这里故意输错了密码,可以发现,执行抛出了异常, 这里的continu代码没再运行,而throw中的代码则会继续执行
$pdo = new PDO('mysql:host=127.0.0.1;dbname=mysql', 'root', 'aa');
var_dump($pdo);
echo '<hr/>';
echo 'continue 代码继续执行';
} catch (PDOException $e) {
echo $e->getMessage();
echo '<hr/>';
echo 'throw 继续执行';
}
//splfile也是这样和pdo的异常处理类似, 类似的还有很多不在逐一测试
try {
$splObj = new SplFileObject('test.txt', 'r');
var_dump($splObj);
echo '<hr/>';
echo 'spl continue 代码继续执行';
} catch (Exception $e) {
echo $e->getMessage();
echo '<hr/>';
echo 'spl throw 继续执行';
echo '<hr/>';
}
/**
* 错误和异常的区别,两个不同的东西
* PHP遇到错误时, 首先触发的时本身的错误, 不会手动抛出异常, 可以通过throw来抛出异常, 然后使用catch来捕获异常
* 错误:
* 没法让错误在调用的时候向上传递, 因为遇到错误必须马上处理.
* 异常:
* 可以--的向上传递知道我们进行捕获,异常也可以自定义处理信息.
*
*/
//多个catch进行嵌套操作
try {
throw new Exception('测试异常1');
} catch (Exception $e) {
echo $e->getMessage();
echo "<hr/>";
try {
throw new Exception('测试异常2');
} catch (Exception $e) {
echo $e->getMessage();
}
}
echo '<hr/>';
echo 'continue...';
- 自定义异常类
/**
* 自定义异常类
* 只需继承Exception的基类即可
* 只有两个方法可以重写
* __contruct 构造方法
* __toString 样式展示
*/
class myException extends Exception
{
/**
* myException constructor.
* @param string $message
* @param int $code
* @param Throwable|null $previous
@author liuhao <lh@btctrade.com>
* 重写构造函数
*/
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
//调用父类构造函数,保证所有变量都被正确的赋值
parent::__construct($message, $code, $previous);
}
/**
* Method __toString
* @desc 重写父类的样式展示方法
*
* @author LiuHao <lh@btctrade.com>
*
* @return void
*/
public function __toString()
{
$message = "<h2>出现异常,异常信息如下</h2>";
$message = "<p>" . __CLASS__ . "[{$this->code}]: {$this->message}</p>";
return $message;
}
public function test()
{
echo 'this is a test';
}
public function stop()
{
exit('script end ...');
}
}
//调用自定义异常类进行处理
try {
echo '出现了异常';
echo "<hr/>";
throw new myException('测试自定义异常', 3);
} catch (Exception $e) {
echo $e->getMessage();
echo "<hr/>";
echo $e;
echo "<hr/>";
echo $e->test();
echo "<hr/>";
// echo $e->stop();
}
echo 'continue ....';
//多个try catch 同时利用自定义异常处理类来进行捕获
// 这里在基类的错误异常类中,也是可以调用子类中定义的方法
// 一般Exception我们都放在最后使用,如果要用的话.
// 不然程序会先进入Exception 就没法用我们自己定义的错误处理类了
try {
echo "<hr/>";
throw new myException('测试自定义异常');
} catch (Exception $e) {
echo "<hr/>";
echo 'Exception';
echo "<hr/>";
echo $e->getMessage();
echo "<hr/>";
echo $e->test();
} catch (myException $e) {
echo "<hr/>";
echo 'myException';
echo $e->getMessage();
}
echo "<hr/>";
echo 'continue ....';
- 自定义文件写入异常类,以及调用自定义异常类写入文件
/**
* Class FileException
*
* @author liuhao <lh@btctrade.com>
* @desc 自定义文件写入异常类
*/
class FileException extends Exception
{
public function getDetailes()
{
switch ($this->code) {
case 0:
return '没有提供文件';
break;
case 1:
return '文件不存在';
break;
case 2:
return '不是一个文件';
break;
case 3:
return '文件不可写';
break;
case 4:
return '非法文件的操作模式';
break;
case 5:
return '数据写入失败';
break;
case 6:
return '文件不能被关闭';
break;
default:
return '非法';
break;
}
}
}
/**
* Class WriteData
*
* @author liuhao <lh@btctrade.com>
* @desc 调用自定义文件写入异常类
*/
class WriteData
{
private $_message = '';
private $_fp;
public function __construct($filename = '', $model = 'w')
{
$this->_message = "文件{$filename} 模式:{$model}";
if (empty($filename)) {
throw new FileException($this->_message, 0);
}
if (!file_exists($filename)) {
throw new FileException($this->_message, 1);
}
if (!is_file($filename)) {
throw new FileException($this->_message, 2);
}
if (!is_writeable($filename)) {
throw new FileException($this->_message, 3);
}
if (!in_array($model, ['w', 'w+', 'a', 'a+'])) {
throw new FileException($this->_message, 4);
}
$this->_fp = fopen($filename, $model);
}
public function write($data)
{
if (!fwrite($this->_fp, $data . PHP_EOL)) {
throw new FileException($this->_message, 5);
}
}
public function close()
{
if ($this->_fp) {
if (!fclose($this->_fp)) {
$this->_fp = null;
throw new FileException($this->_message, 6);
}
}
}
public function __destruct()
{
// TODO: Implement __destruct() method.
$this->close();
}
}
try {
//文件不存在
// $fp = new WriteData('text.txt', 'w');
//没有传文件
// $fp = new WriteData();
$fp = new WriteData('./log/log.txt', 'a+');
$fp->write('this is test');
} catch (FileException $e) {
echo '出现问题了' . $e->getMessage() . '; 详细情况如下' . $e->getDetailes();
}
- 简单的记录和发送异常信息
/**
* Class LogException
*
* @author liuhao <lh@btctrade.com>
* @desc 简单的记录和发送异常信息
*/
class LogException extends Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code);
//保存错误信息到文件
error_log($this->getMessage(), 3, './log/log.txt');
}
}
//这时候会爆出waring错误信息, 同时也会写入到错误文件当中
//此处以MySQL连接发生错误为例
try {
$link = mysqli_connect('127.0.0.1', 'root11', '');
if (!$link) {
throw new LogException('数据库连接失败');
}
} catch (LogException $e) {
$e->getMessage();
}
利用观察者模式处理异常信息
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/1 下午9:22
* @Email: lh@btctrade.com
* @File: Exception_Observer.php
* @Desc: 观察者的接口, 主要是让所有的观察者都来遵循该接口的规范,不管以后有多少个观察者都要使用该接口
*/
interface Exception_Observer
{
/**
* Method update
* @desc 通过类型限制, 强制限制必须来自该Observable_Exception对象的
*
* @author LiuHao <lh@btctrade.com>
* @param Observable_Exception $e
*
* @return mixed
*/
public function update(Observable_Exception $e);
}
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/1 下午9:48
* @Email: lh@btctrade.com
* @File: Logging_Exception.php
* @Desc: 记录日志文件的观察者
*/
class Logging_Exception_Observer implements Exception_Observer
{
protected $_filename = './error/logException.log';
/**
* Logging_Exception_Observer constructor.
* @param null $fileName
* @author liuhao <lh@btctrade.com>
* 构造函数
*/
public function __construct($fileName = null)
{
if (!empty($fileName) && is_string($fileName)) {
$this->_filename = $fileName;
}
}
/**
* Method update
* @desc 完善父类接口中的该方法, 同时父类中的改方法是含有类型限制的,仅限Observable_Exception类的对象使用
*
* @author LiuHao <lh@btctrade.com>
* @param Observable_Exception $e
*
* @return void
*/
public function update(Observable_Exception $e)
{
// TODO: Implement update() method.
$message = "时间: " . date('Y-m-d H:i:s', time()) . PHP_EOL;
$message .= "信息: " . $e->getMessage() . PHP_EOL;
$message .= "追踪信息: " . $e->getTraceAsString() . PHP_EOL;
$message .= "文件: " . $e->getFile() . PHP_EOL;
$message .= "行号" . $e->getLine() . PHP_EOL;
$message .= '------------------------------' . PHP_EOL;
//写入日志文件,error_log的类型为3位写入文件
error_log($message, 3, $this->_filename);
}
}
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/1 下午10:04
* @Email: lh@btctrade.com
* @File: Emailling_Exception_Observer.php
* @Desc: 邮件发送的观察者
*/
class Emailling_Exception_Observer implements Exception_Observer
{
protected $_email = 'email@email.com';
public function __construct($email = null)
{
if ($email != null && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->_email = $email;
}
}
/**
* Method update
* @desc 完善父类接口中的该方法, 同时父类中的改方法是含有类型限制的,仅限Observable_Exception类的对象使用
*
* @author LiuHao <lh@btctrade.com>
* @param Observable_Exception $e
*
* @return void
*/
public function update(Observable_Exception $e)
{
// TODO: Implement update() method.
$message = "时间: " . date('Y-m-d H:i:s', time()) . PHP_EOL;
$message .= "信息: " . $e->getMessage() . PHP_EOL;
$message .= "追踪信息: " . $e->getTraceAsString() . PHP_EOL;
$message .= "文件: " . $e->getFile() . PHP_EOL;
$message .= "行号" . $e->getLine() . PHP_EOL;
//发送邮件,errlr_log类型为1为发送邮件, 暂时没有配置所以注释掉了
// error_log($message, 1, $this->_email);
var_dump('邮件发送成功');
}
}
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/1 下午8:49
* @Email: lh@btctrade.com
* @File: Observable_Exception.php
* @Desc: 观察者的类,继承Exception_Observer接口基类
*/
class Observable_Exception extends Exception
{
//定义静态属性,保存观察者的信息
public static $_observers = array();
/**
* Method attch
* @desc 用以添加观察者
*
* @author LiuHao <lh@btctrade.com>
* @static
* @param Exception_Observer $observer 通过类型暗示进行限制传参
*
* @return void
*/
public static function attch(Exception_Observer $observer)
{
//动态添加观察者成员
self::$_observers[] = $observer;
}
/**
* Observable_Exception constructor.
* @param string $message
* @param int $code
* @param Throwable|null $previous
* @author liuhao <lh@btctrade.com>
* 在构造函数中, 当程序发生异常, 通知各位观察者
*/
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
//如果这里没有形参, 或者形参设置不对, 会导致输出的信息不正确
//次数的message设置为什么, 后面输出的message就是什么
parent::__construct($message, $code, $previous);
//通知观察者
$this->notify();
}
/**
* Method notify
* @desc 异常通知方法
*
* @author LiuHao <lh@btctrade.com>
*
* @return void
*/
public function notify()
{
//遍历所有观察者,逐个发送错误信息
foreach (self::$_observers as $observer) {
//每一个观察者都调用一下update方法,update方法来自Exception_Observer类
//该类中的该方法有类型限制,定义了必须来自Observable_Exception类的对象
$observer->update($this);
}
}
}
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/1 下午10:09
* @Email: lh@btctrade.com
* @File: test_Exception.php
* @Desc: 异常处理观察者模式, 测试文件
*/
//include 'Exception_Observer.php';
//include 'Observable_Exception.php';
//include 'Logging_Exception_Observer.php';
//include 'Emailling_Exception_Observer.php';
//添加观察者, attch有类型
Observable_Exception::attch(new Logging_Exception_Observer());
//添加第二个观察者,同时制定日志写入的位置
Observable_Exception::attch(new Logging_Exception_Observer('./error/log_Exception1.log'));
//添加第三个观察者
Observable_Exception::attch(new Emailling_Exception_Observer());
/**
* Class MyException
*
* @author liuhao <lh@btctrade.com>
* @desc 测试用来抛出异常信息
*/
class MyException extends Observable_Exception
{
public function test()
{
echo 'this is a test ';
}
public function test1()
{
echo '我是自定义来处理的异常';
}
}
try {
throw new MyException('出现异常信息, 记录一下');
} catch (MyException $e) {
echo '</hr>';
echo $e->getMessage();
echo '</hr>';
$e->test();
echo '</hr>';
$e->test1();
}
自定义异常处理器
set_exception_handler — 设置用户自定义的异常处理函数
设置默认的异常处理程序,用于没有用 try/catch 块来捕获的异常。 在 exception_handler 调用后异常会中止。
使用try catch正常捕捉的,不能被set_exception_handler捕捉
restore_exception_handler — 恢复之前定义过的异常处理函数。
在使用 set_exception_handler() 改变异常处理函数之后,此函数可以 用于还原之前的异常处理程序(可以是内置的或者也可以是用户所定义的函数)。
例如上面使用了两次set_exception_handler()
这里第一次使用restore_exception_handler()只能恢复到第一次set;
再次restore的时候,才可以彻底取消set
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/3 上午1:18
* @Email: lh@btctrade.com
* @File: myException.php
* @Desc: 自定义异常处理函数,用以处理未被捕获的异常,即没有放在try catch中的异常 set_exception_handler()
* set_exception_handler(); 设置一个用户定义的异常处理函数,和错误处理set_error_handler()用户一样
* restore_exception_handler(); 恢复到上一次定义过的异常处理函数
*
*/
function exceptionHandler_1(Exception $e)
{
echo '自定义异常处理器1 函数名: ' . __FUNCTION__ . PHP_EOL;
echo '异常信息: ' . $e->getMessage() . PHP_EOL;
}
function exceptionHandler_2(Exception $e)
{
echo '自定义异常处理器2 函数名: ' . __FUNCTION__ . PHP_EOL;
echo '异常信息: ' . $e->getMessage() . PHP_EOL;
}
//这里没有使用try catch 也没有在之前, 设置set_exception_handler, 所以会抛出一个致命错误
//throw new Exception('这里遇到了异常');
//这里会正常的被exceptionHandler_1捕捉到异常信息
//set_exception_handler('exceptionHandler_1');
//throw new Exception('再次抛出一个异常信息');
//这里会正常的被exceptionHandler_2捕捉到异常信息
set_exception_handler('exceptionHandler_1');
set_exception_handler('exceptionHandler_2');
//加了这个之后,这里的异常就会被exceptionHandler_1捕捉到, 再次restore_则会返回到未设置状态
//注意该函数前面必须至少有一个set_exception_handler 不是必须紧紧挨着
restore_exception_handler();
throw new Exception('又再次抛出一个异常信息');
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/4 上午4:43
* @Email: lh@btctrade.com
* @File: myException_class.php
* @Desc: 自定义异常处理类
*/
class ExceptionHandler
{
protected $_exception;
public function __construct(Exception $e)
{
$this->_exception = $e;
}
public static function handler(Exception $e)
{
$self = new self($e);
$self->log();
echo $self;
}
public function log()
{
error_log($this->_exception->getMessage() . PHP_EOL, 3, './myException.log');
}
public function __toString()
{
// TODO: Implement __toString() method.
//这里的eof结尾必须顶格,否则报错
$message = <<<EOF
恭喜你,爆出异常了, 哈哈哈哈.
EOF;
return $message;
}
}
set_exception_handler(['ExceptionHandler', 'handler']);
//这里正确被try catch捕捉到的异常信息, 不会被set_exception_handler捕捉
try {
throw new Exception('正确被trye catch捕捉的异常信息');
} catch (Exception $e) {
echo $e->getMessage() . PHP_EOL;
}
//抛出的异常成功被ExceptionHandler捕捉,同时同时显示了出来
throw new Exception('测试自定义异常信息');
如何像处理异常那样处理错误
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/4 上午5:21
* @Email: lh@btctrade.com
* @File: ErrorException.php
* @Desc: 利用PHP自带的ErrorException把错误像异常一样进行处理
*/
function exception_error_handler($errno, $errstr, $errfile, $errline)
{
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('exception_error_handler');
//此处这里如果没有try catch的话,无法调用到set_error_handler中设置的exception_error_handler函数
try {
echo gettype();
} catch (Exception $e) {
echo $e->getMessage();
}
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/4 上午5:56
* @Email: lh@btctrade.com
* @File: ErrorToException.php
* @Desc: 自定义错误捕捉类, 想捕捉异常一样去捕捉
*/
class ErrorToException extends Exception
{
public static function handle($errno, $errstr)
{
throw new self($errstr, $errno);
}
}
//set_error_handler(['ErrorToException', 'handle']);
//设置捕捉错误的, 错误等级, 只捕捉下面两种类型的错误
set_error_handler(['ErrorToException', 'handle'], E_USER_NOTICE | E_NOTICE);
try {
//因为上面限制了错误信息的捕捉级别, 而这里的gettype是一个waring所以不会被捕捉
//但是$test属于Notice, 就会被捕捉的到
//trigger_error抛出的时一个用户级别的Notice所以也会被捕捉的到,但是由于$test被捕捉了,所以不会向下执行
//如果注释掉$test就会明白这一点
echo gettype();
echo PHP_EOL;
echo $test;
echo PHP_EOL;
trigger_error('tes*t', E_USER_NOTICE);
} catch (Exception $e) {
echo $e->getMessage();
}
在发生异常时将用户重定向到另一个页面
<?php
/**
* @Author: LiuHao
* @Date: 2018/3/4 上午6:40
* @Email: lh@btctrade.com
* @File: ExceptionRedict.php
* @Desc: 将异常进行重定向到404页面
*/
class ExceptionRedict
{
public $redirect = '404.html';
protected $_exception;
public function __construct(Exception $e)
{
$this->_exception = $e;
}
public static function handle(Exception $e)
{
$self = new self($e);
$self->log();
//循环清除缓冲信息, 如果没有缓冲信息会抛出一个警告错误, 这一块需要我们屏蔽一下错误
while (@ob_end_clean()) {
};
header('HTTP/1.1 307 Temporay Redirct');
header('Cache-Control:no-cache,must-revalidate');
header('Expire:Sat 28 Mar 2015 13:28:48 GMT');
header('Location:' . $self->redirect);
exit(1);
}
public function log()
{
error_log($this->_exception->getMessage() . PHP_EOL, 3, './ExceptionRedict.log');
}
}
set_exception_handler(['ExceptionRedict', 'handle']);
$link = mysqli_connect('127.0.0.1', 'rootadsf', 'root');
if (!$link) {
throw new Exception('数据库可能被攻击');
}