上一篇:那些年,PHPer遇到的错误与异常:上篇之错误
一、PHP中的异常简介及使用
1.1 异常执行流程
try
{
// 需要进行异常处理的代码段;
throw 语句抛出异常;
}catch( Exception $e )
{
...
}
catch( Exception $e )
{
// 处理异常
}
contine.....
未被捕获的异常会报致命错误:Fatal error:Uncaught exception.....
1.2 PHP异常特点
-
PHP
不会主动捕获异常,需要程序中主动抛出 (throw
)异常,才能捕获。 -
throw
会自动向上抛出 -
throw
之后的语句不会执行 -
try
后必须有catch
,否则解析错误Parse error
try{
$num1=3;
$num2=0;
if($num2==0){
throw new Exception('0不能当作除数');
echo 'this is a test';//看不到
}else{
$res=$num1/$num2;
}
}catch(Exception $e){
echo $e->getMessage();
}
1.3 PHP内置异常
Php
不像java
提供了很多异常类,所以很多异常都会当成错误。要想变错误为抛出异常,需要手动throw
异常对象
PHP内置异常如:PDOException
、SplFileObject
可以自动抛出异常,后面的代码可以继续执行。
1.4 错误和异常的区别
1.4.1 异常处理
当异常被抛出,throw
后的代码不会继续执行,PHP
会尝试查找匹配的 catch
代码块。如果异常没有被捕获,而且又没用使用set_exception_handler()
作相应的处理的话,那么将发生一个严重的错误(致命错误),并且输出 “Uncaught Exception
” (未捕获异常)的错误消息。
1.4.2 异常的基本语法结构
try
- 需要进行异常处理的代码应该放入try
代码块内,以便捕获潜在的异常。如果没有触发异常,则代码将照常继续执行。但是如果异常被触发,会抛出一个异常
throw
- 这里规定如何触发异常。每一个try或 throw 必须
对应至少一个 catch。使用多个catch代码块可以捕获不同种类的异常。
catch
- catch代码块会捕获异常,并创建一个包含异常信息的对象
1.4.3 重新抛出异常
有时,当异常被抛出时,也许希望以不同于标准的方式对它进行处理。可以在一个 catch 代码块中再次抛出异常。注意再次抛出异常需要try{}catch{},不能直接在catch代码块中throw异常
。
脚本应该对用户隐藏系统错误。对程序员来说,系统错误也许很重要,但是用户对它们并不感兴趣。为了让用户更容易使用,您可以再次抛出带有对用户比较友好的消息的异常。
简而言之:如果抛出了异常,就必须捕获它。
1.4.4 错误与异常的区别
异常:程序运行与预期不太一致错误:触发的是本身的错误
- 当遇到错误的时候,触发的是本身的错误,不会自动的抛出异常。异常可以通过
throw
语句抛出异常,通过catch
捕获异常,如果未捕获会产生致命错误。 - 错误在发生的时候或触发的时候,必须马上对脚本进行处理。异常可以一一向上传递,直到被捕获,再处理。
- 错误触发不具有相关代码或名称。异常可以自定义处理错误信息(异常的好处就体现出来了),是通过代码来抛出,捕获然后处理
二、自定义异常类
2.1 自定义异常类
- 自定义异常类只能重写构造函数和
toString
两个函数 - 自定义异常类可以增加自己的方法
- 多个
catch
时,一般Exception
基类放在最后,基类可以调用自定义异常类定义的方法
/**
* 自定义异常类
* Class MyException
*/
class MyException extends Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
public function __toString()
{
$message = "出现异常了,信息如下
";
$message .= "".__CLASS__."[{$this->code}]:{$this->message}
";
return $message;
}
public function test()
{
echo 'this is a test';
}
public function stop()
{
exit('script end...');
}
//自定义其它方法
}
try{
echo '出现异常啦';
throw new MyException('测试自定义异常');
}catch (MyException $exception){
echo $exception->getMessage();
echo $exception;
}
//会继续执行
echo 'continue.........';
try{
throw new MyException('测试自定义异常');
}catch (Exception $exception){
echo $exception->getMessage();
$exception->test();
} catch (MyException $exception){
echo $exception->getMessage();
}
2.2 小技巧
//将错误用错误抑制符吸收,然后抛出异常
If(@!fwrite($filename,$data)) throw new exception(自定义异常)
PHP_EOL #换行符
记录错误日志信息方式:
(1) :file_put_contents(LOG_PATH.'error.log';, '错误信息'.' '.date('Y-m-d H:i:s')."\r\n", FILE_APPEND);
(2) :error_log('错误信息'.' '.date('Y-m-d H:i:s')."\r\n",3,LOG_PATH.'error.log');
2.3 使用观察者模式处理异常信息
Exception_Observer.php
/**
* 给观察者定义规范
*
* Interface Exception_Observer
*/
interface Exception_Observer
{
public function update(Observable_Exception $e);
}
Observable_Exception.php
/**
* 定义观察者
* Class Observable_Exception
*/
class Observable_Exception extends Exception
{
//保存观察者信息
public static $_observers = array();
public static function attach(Exception_Observer $observer)
{
self::$_observers[] = $observer;
}
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->notify();
}
public function notify()
{
foreach (self::$_observers as $observer) {
$observer->update($this);
}
}
}
Logging_Exception_Observer.php
/**
* 记录错误日志
* Class Logging_Exception_Observer
*/
class Logging_Exception_Observer implements Exception_Observer
{
protected $_filename = __DIR__.'/error_observer.log';
public function __construct($filename = null)
{
if ($filename!==null && is_string($filename)){
$this->_filename = $filename;
}
}
public function update(Observable_Exception $e)
{
$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;
error_log($message,3,$this->_filename);//写到日志中
}
}
test.php
/**
*测试
*/
header('content-type:text/html;charset=utf-8');
require_once 'Exception_Observer.php';
require_once 'Logging_Exception_Observer.php';
require_once 'Observable_Exception.php';
Observable_Exception::attach(new Logging_Exception_Observer());
class MyException extends Observable_Exception{
public function test()
{
echo 'this is a test';
}
}
try{
throw new MyException('出现了异常!');
}catch (MyException $exception){
echo $exception->getMessage();
}
三、自定义异常处理器
3.1 如何自定义异常处理器
3.1.1 自定义异常处理器
- 类似
set_error_handler
接管系统的错误处理函数,set_exception_handler
接管所有没有被catch
的异常 -
restore_exception_handler
同restore_error_handler
一样,本质上应该说从异常/错误处理函数栈中弹出一个。比如有一个异常处理函数,弹出一个的话,就没有异常处理函数,如果有异常没有捕获,会交由错误处理函数,如没有错误处理函数,异常最终会有系统错误处理函数处理。如果设置了2个异常处理函数,弹出一个,会交由下面一个异常处理函数处理。
/**
* 自定义异常函数处理器
*/
header('content-type:text/html;charset=utf-8');
function exceptionHandler_1($e)
{
echo '自定义异常处理器1
函数名:'.__FUNCTION__.PHP_EOL;
echo '异常信息:'.$e->getMessage();
}
function exceptionHandler_2($e)
{
echo '自定义异常处理器2
函数名:'.__FUNCTION__.PHP_EOL;
echo '异常信息:'.$e->getMessage();
}
set_exception_handler('exceptionHandler_1');
//set_exception_handler('exceptionHandler_2');
//恢复到上一次定义过的异常处理函数,即exceptionHandler_1
//restore_exception_handler();
//致命错误信息
//restore_exception_handler();
throw new Exception('测试自定义异常处理器');
//自定义异常处理器,不会向下继续执行,因为throw之后不会再继续执行;try{} catch{}之后,会继续执行
//回顾:自定义错误处理器会继续执行代码,而手动抛出的错误信息不会继续执行
echo 'test';
/**
* 自定义异常类处理器
* Class ExceptionHandler
*/
class ExceptionHandler
{
protected $_exception;
protected $_logFile = __DIR__.'/exception_handle.log';
public function __construct(Exception $e)
{
$this->_exception = $e;
}
public static function handle(Exception $e)
{
$self = new self($e);
$self->log();
echo $self;
}
public function log()
{
error_log($this->_exception->getMessage().PHP_EOL,3,$this->_logFile);
}
/**
* 魔术方法__toString()
* 快速获取对象的字符串信息的便捷方式,直接输出对象引用时自动调用的方法。
* @return string
*/
public function __toString()
{
$message = <<
Title
出现异常了啊啊啊啊
EOF;
return $message;
}
}
set_exception_handler(array('ExceptionHandler','handle'));
/**
* try catch不会被自定义异常处理!!!!
*/
try{
throw new Exception('this is a test');
}catch (Exception $exception) {
echo $exception->getMessage();
}
throw new Exception('测试自定义的异常处理器');
3.1.2 错误/异常之后是否继续执行代码问题总结
异常:
自定义异常处理器不会向下继续执行,因为throw
之后不会再继续执行
try{} catch{}
之后,会继续执行错误:
自定义错误处理器会继续执行代码,而手动抛出的错误信息不会继续执行
3.2 像处理异常一样处理PHP错误
3.2.1 方式一:ErrorException
/**
* 方式一:ErrorException错误异常类
* @param $errno
* @param $errstr
* @param $errfile
* @param $errline
* @throws ErrorException
*/
function exception_error_handler($errno,$errstr,$errfile,$errline){
throw new ErrorException($errstr,0,$errno,$errfile,$errline);
}
set_error_handler('exception_error_handler');
try{
echo gettype();
}catch (Exception $exception){
echo $exception->getMessage();
}
3.2.2 方式二:自定义异常类,继承基类Exception
/**
* 方式二:自定义异常类
* Class ErrorToException
*/
//显示所有的错误
error_reporting(-1);
class ErrorToException extends Exception{
public static function handle($errno,$errstr)
{
throw new self($errstr,0);
}
}
set_error_handler(array('ErrorToException','handle'));
set_error_handler(array('ErrorToException','handle'),E_USER_WARNING|E_WARNING);
try{
echo $test;//notice,不会被处理
echo gettype();//warning
//手动触发错误
trigger_error('test',E_USER_WARNING);
}catch (Exception $exception){
echo $exception->getMessage();
}
3.3 PHP页面重定向实现
header('Content-type:text/html;charset=utf-8');
class ExceptionRedirectHandler{
protected $_exception;
protected $_logFile = __DIR__.'redirect.log';
public $redirect='404.html';
public function __construct(Exception $e){
$this->_exception=$e;
}
public static function handle(Exception $e){
$self=new self($e);
$self->log();
// ob_end_clean()清除所有的输出缓冲,最后没有缓存的时候会产生通知级别的错误
while(@ob_end_clean());
header('HTTP/1.1 307 Temporary Redirect'); //临时重定向
header('Cache-Control:no-cache,must-revalidate');//no-cache强制向源服务器再次验证,must-revalidate可缓存但必须再向源服务器进行确认
header('Expires: Sat, 28 Mar 2016 13:28:48 GMT'); //资源失效的时间
header('Location:'.$self->redirect); //跳转
}
public function log(){
error_log($this->_exception->getMessage().PHP_EOL,3,$this->_logFile);
}
}
set_exception_handler(array('ExceptionRedirectHandler','handle'));
$link=@mysqli_connect('127.0.0.1','root','1234561');
if(!$link){
throw new Exception('数据库连接出错啦');
}
完!