PHP错误和异常处理

[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('数据库可能被攻击');
}


转载于:https://my.oschina.net/chinaliuhan/blog/3064490

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值