PHP的异常处理是到底什么?

想写这篇文章很久了,由于生活上的一些事情,一直搁置着
说实话,有很多学PHP的同学对异常并不了解,甚至觉得不需要这个东西,我觉得只能说明这部分同学还没有比较好的oop思想,如果想学好PHP,对异常的了解是必不可少的

一、什么是异常?

  异常和错误是不一样的,错误常常是不可避免的,就像是程序的bug,谁又能一次性写出没有bug的程序呢?我们在做事情的时候,常常不能一帆风顺,总有可能出现一些无法避免的问题,但是这些问题又不一定必然发生,只能说有存在发生的可能性,是无法预估的,在程序中,我们一般将这种可能发生的事情称之为程序的异常,举个简单的例子,例如常见的参数错误问题,调用方法的人不见得愿意按照你的想法给你参数,那么这种情况就是一种可能发生的事情,再举一个例子,用代码打开一个文件时,是可能出现很多问题的,例如该文件已被占用,该文件不存在,这些情况也属于可能发生的事情。

二、为什么要捕捉异常

  既然遇到了无法确定的事情,我们必须需要对这种可能发生的事情做一手准备,我们可以简单当做这就是对异常的处理,当然处理方式确实有很多种,异常抛出只是其中一种方式,你可以直接终止程序执行打印出信息报错,也可以直接写日志然后die掉,但是考虑到这些方式无法对上层调用栈进行通知,常常不是很可取的方式。一般来说我们会在方法进行异常的捕捉并抛出或不抛出直接处理,直接抛出的好处是可以进行统一的异常处理,PHP跟其他高级语言一样,提供了一个异常的处理类,我们称之为根异常类 \Exception,后面我们可以研究一下这个类到底实现了什么?不过你得先了解怎么使用好PHP的异常处理再考虑到研究其源码。

三、如何捕捉异常

跟很多语言一样,PHP也是使用try…catch…finally的结构来捕捉异常,大致的写法是这样的:

<?php
	class Test
	{
		public function index()
		{
			try{
			  //省略对应操作  
			}catch(\Exception $e) {
			   echo "报错为:".$e->getMessage()."<br/>"
					.$e->getFile().'('.$e->getLine().')'; //打印异常信息
			}finally{
				//这里是一定会执行的代码,一般来说都不用,可以省略finally
			}
		}
	}

这时候你会疑问,$e是什么,getMessage()这个方法又是什么?现在让我们看看这个类有什么?

class Exception implements Throwable {
	/* Properties */
    protected $message;
    protected $code;
    protected $file;
    protected $line;

	/* Methods */
	public __construct ([ string $message = "" [, int $code = 0 [, Throwable $previous 						   = NULL ]]] )
	final public getMessage ( void ) : string
	final public getPrevious ( void ) : Throwable
	final public getCode ( void ) : mixed
	final public getFile ( void ) : string
	final public getLine ( void ) : int
	final public getTrace ( void ) : array
	final public getTraceAsString ( void ) : string
	public __toString ( void ) : string
	final private __clone ( void ) : void
}
interface Throwable
{
    public function getMessage();

    public function getCode();

    public function getFile();

    public function getLine();

    public function getTrace();

    public function getTraceAsString();

    public function getPrevious();

    public function __toString();
}

  从上面的代码我们看到,这个根异常类实现了一个Throwable接口,这个接口只是限定了一些必须实现的方法,这些方法其实常用的也就只有getCode(),getMessage()方法。眼尖的你会看到,异常类的方法(除了__toString )都是带上了final,说明这些方法子类是无法覆盖的,这时我们在看看它的四个常量,分别是message,code,file,line,意思分别是异常的信息,异常的状态码,异常发生的文件路径,发生代码的行数,这四个信息基本可以帮助我们找到异常发生的位置以及发生的原因。
&  这时候你可能会疑问几个问题,我在此列出来:

1.$e这个对象是什么,是在那里被实例化的
2.catch的作用是什么
3.如果try里面包含的代码没有主动throw,那么异常捕捉还有用吗?
4.如果主动throw了,但是没有地方进行异常的捕捉怎么办
5.嵌套的捕捉会怎么样呢?


现在让我们带着问题去熟悉吧!

1.e这个对象实际上是try块里面所抛出的一个实例异常对象,它可能是属于\Exception的对象,也有可能是它的子类对象,

2.实际上catch的作用就类似与instance of 和switch的结合,用来判断这个$e是属于哪个异常类的对象,进而做不同的处理操作,这里你可以看起来有点懵,等会你看我下面的示例代码应该就明白了。

3.如果try里面没有主动throw的地方,异常捕捉就没有什么用了,因为都捕捉不到,这时候IDE应该会有提示。

4.如果主动抛出了异常,但是没有去主动捕捉,这时set_exception_handler(exception_function)方法就起作用了,我们先把文档该方法定义搬出来:

  set_exception_handler() 函数设置用户自定义的异常处理函数。该函数用于创建运行期间的用户自己的异常处理方法。该函数返回旧的异常处理程序,如果失败则返回 NULL。

exception_function:  必需。规定未捕获的异常发生时调用的函数。 该函数必须在调用 set_exception_handler() 函数之前定义。这个异常处理函数需要需要一个参数,即抛出的 exception 对象。

可能有人看的有点模糊,我简单解释一下,大概意思就是,我们可以写一个方法,里面的参数是一个抛出的exception对象,在方法体里面我们可以做些想做的操作,例如 写日志,输出异常信息等
  然后我们可以在入口文件或者某个需要的地方去调用set_exception_handler函数,将方法名作为参数传递给他。这样的话当我们抛出了异常,但是没有去捕捉的话,就会默认调用我们的处理方法。这一点真心不错,万一写代码不规范忘了try..catch呢。

5.多层的try..catch实际上和多层if是一个意思,PHP会优先找离异常代码最近的,这个最近的,我觉得我应该不用解释了吧!

四、如何自定义异常类

  首先,我们要考虑一下为什么要自定义异常类,PHP默认提供了一个\Exception类给我们用,但是实际上我们业务是需要给异常分类的,例如分成参数验证的异常类,调接口的异常类,文件读写的异常类,因此我们需要自定义这些不同的异常类,实际上我们从上面\Exception的方法可知,基本上所有的方法都是子类无法覆盖了,这也说明,我们自定义异常类,只需要单纯继承\Exception类即可,不需要做任何操作,这样还讲的不清楚?代码扔出来好了。

<?php
	class ApiException extends \Exception
	{
	}
<?php
	class ParamsException extends \Exception
	{
	}

以上省略命名空间。
抛出异常可以这么做:

<?php
	......省略一堆代码
	throw new ApiException("API异常出错,错误信息为:xxxxxx");

捕捉异常可以这么做:

<?php
	try {
		...省略一堆代码,代码里面有throw操作
		
	} catch(ApiException $ae) {
		echo $ae->getMessage();
	} catch(ParamsException $pe) {
		echo $pe->getMessage();
	} catch(\Exception $e) {
	   echo $e->getMessae();
	}

这里又有一个问题了,如果自定义了异常类,例如上面的ApiException 类,但是没有写对应的catch,那么会怎么样呢?实际上,PHP会尝试找其父类匹配,也就是\Exception类,这也就是说,所有的自定义异常类如果没有对应的catch捕捉的话,最终都可以被\Exception所在的catch捕捉到,毕竟人家是祖宗嘛。

在平时的开发中,突然意识了一个问题,异常的抛出处理会不会终止程序的执行呢?
  带着这个问题,我们可以写个小栗子进行验证(但是有个地方需要注意的是,同个方法里,如果抛出了异常,那么异常后面的代码是不会执行的,这个很容易证明,因此,我们只需要验证,异常是否会终止程序的执行):

<?php
	class Test
	{
		public function index()
		{
			for($i = 0; $i<10; $i++)
			{
				$this->printNumber($i);
			}
			echo "-----over";
		}
   		public function printNumber($i);
   		{
   		    try{
   		    	echo $i;
   				throw new \Exception("异常");//下面的语句不会执行
   				echo "---";
   		    } catch(\Exception $e) {
   		           echo  $e->getMessage();
   		    }
   		}
	}
	$a = new Test();
	$a->index();

执行的结果是:
0异常1异常2异常3异常4异常5异常6异常7异常8异常9异常
-----over

  这说明什么呢?说明异常的捕捉并不会终止程序的执行,只是在抛出异常的位置将程序控制权暂时交予了catch语句内的代码,当catch语句的代码执行完毕后,又是正常的调用栈过程。

写到了这里,不晓得你对异常是否有了一定的认识,欢迎在下方评论区发表您的看法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MClink

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值