【异常和错误】PHP的异常和错误处理【原创】

摘要:主要是参考《PHP核心技术与最佳实践》的第一章 面向对象思想的核心概念的1.6小节

1.1 PHP的异常处理机制

在语言级别上,通常具有许多错误处理模式,但这些处理模式往往简历在约定俗成的基础上,即这些错误是预知的。但是在大型程序中,如果每次调用都需要去逐一检查错误,会使代码变得复杂,到处充斥着if...else,并且严重降低代码的可读性,而且人的因素也是不可依赖的,程序员可能并不会把这些问题当一回事,从而导致业务异常。在这种背景下,逐渐形成了异常处理机制。

PHP中的异常,是程序运行中不符合预期的情况及与正常流程不同的状况。一种不正常的情况,就是按照正常逻辑不该出错但仍然出错的情况,这属于逻辑和业务流程的一种中断,而不是语法错误。PHP中的错误则属于自身问题,是一种非法语法或者是环境问题导致的、编译器无法通过检查甚至是无法运行的情况。

在各种语言中,异常(exception)和错误(error)的概念是不一样的。PHP中,遇到任何自身错误都会触发错误,而不是抛出异常。PHP一旦遇到非正常的代码,通常都是触发错误,而不是抛出异常。在这个意义上,如果想使用异常处理机制来处理不可预料的问题,是做不到的。比如,想在文件不存在的情况下或者是数据库连接打不开的情况下触发异常是不可行的,在PHP中会把它作为错误抛出,而不会作为异常自动捕获。

比如下面的经典的除零问题:
<?php

$a = null;
try {
    $a = 5 / 0;
    echo $a , PHP_EOL;
} catch (exception $e) {
    $e->getMessage();
    $a = -1;
}

echo $a;
运行

Warning: Division by zero in C:\laragon\www\php_book\1_16_exception.php on line 13

Call Stack:
    0.0010     349728   1. {main}() C:\laragon\www\php_book\1_16_exception.php:0


可以看到, 对于除零这种“异常”情况,PHP认为这是一个错误,直接触发错误,而不会自动抛出异常使程序进入异常流程,即没有进入异常分支,也没有处理异常。PHP只有在你主动throw后,才会捕获异常。也就是说,PHP通常是无法自动 捕获有意义的异常的,它把所有不正常的情况都视为错误,而如果想要捕获这个异常呢,就需要使用if...else结构,保证代码是正常的,然后判断如果除数为0的话,则手动抛出异常,再去捕获。

PHP中的异常机制是不足的,绝大多数情况下无法自动抛出异常,必须用if...else先进行判断,再手动抛出异常。而手动抛异常的意义其实并不大,因为这意味着在代码中已经充分预期到错误的出现,也就算不上真正的‘异常’,同时,这种方式还会使业务的逻辑判断和处理变成纷繁复杂。

注意:Java有一套完整的异常机制,内置很多异常类会自动捕获各种各样的异常,不需要程序员去判断各种异常情况后再手动抛出,编译器会代替程序员进行判断业务是否发生错误,如果发生了则自动抛出异常。所以对于上面的除零问题,Java也可以捕获到而不像PHP那样会抛出错误。

1.2 PHP的异常处理用法

下面使用PHP的异常处理来对表单进行分门别类的处理:
<?php

class emailException extends exception
{

}

class pwdException extends exception
{
    public function __toString()
    {
        // 改写抛出异常结果
        return "异常 {$this->getCode()} : {$this->getMessage()} in File : {$this->getFile()} on line : {$this->getLine()}" . PHP_EOL;
    }
}

function reg($reg_info = null)
{
    if (empty($reg_info) || !isset($reg_info)) {
        throw new Exception('参数非法');
    }

    if (empty($reg_info['email'])) {
        throw new emailException('邮件为空');
    }

    if ($reg_info['pwd'] != $reg_info['repwd']) {
        throw new pwdException('两次密码不一致');
    }

    echo '注册成功';
}

try {
    reg(['email' => '8448@qq.com', 'pwd' => 123456, 'repwd' => 12345678]);
} catch (emailException $ee) {
    echo $ee->getMessage();
} catch (pwdException $ep) {
    echo PHP_EOL , $ep;
    echo '特殊处理';
} catch (Exception $e) {
    echo $e->getTraceAsString();
    echo PHP_EOL , '其他情况,统一处理';
}


运行:
异常 0 : 两次密码不一致 in File : C:\laragon\www\php_book\1_18_exception.php on line : 35
特殊处理


在这里,对表单进行异常处理,通过重写异常类、手动抛出异常的方式进行异常处理,这是一种业务异常。

PHP中使用异常处理机制的场景:
  • 对程序的悲观预测:比如说这段代码在高并发下可能会产生死锁,那么可以悲观的抛出异常,然后在死锁的时候进行捕获,对异常进行细致的处理
  • 程序的需要和对业务的关注:异常是业务处理中必不可少的环节,不能对异常视而不见
  • 语言级别的健壮性要求:一旦异常发生后,需要使用try...catch把异常造成的逻辑中断破坏降低到最小范围内,并且经过补救处理后不影响业务逻辑的完整性,乱抛异常和只抛异常而不捕获,或者捕获而不补救,会导致数据混乱

1.3 捕获异常的两种方式

比如有个上传文件的业务需求,要把上传的文件保存到一个目录里,并且在数据库中插入这个文件的记录,那么这两步就是互相关联、密不可分的一个集成的业务,缺一不可。使用异常的代码如下:
<?php

try {
    // 可能出错的代码段
    if (文件上传不成功) {
     	throw(上传异常);
	} 

	if (插入数据库不成功) {
		throw(数据库操作异常);
	}
} catch (异常) {
	// 必须的补救措施,如删除文件、删除数据库插入记录	
}

// ....


也可以使用这样的方式:
<?php

上传 
{
	if (文件上传不成功) {
     	throw(上传异常);
	} 

	if (插入数据库不成功) {
		throw(数据库操作异常);
	}	

	// ....
}

try {
    上传;
    // ....
} catch (上传异常) {
	// 必须的补救措施,如删除文件、删除数据库插入记录	
} catch (其他异常) {
	// 记录日志等
}


上面两种捕获异常的方式,前一种是在异常发生时立刻捕获,后一种是分散抛异常集中捕获。

如果业务很重要,那么异常就需要越早处理约好,以保证程序在意外情况下保持业务处理的一致性。比如说一个操作有多个前提步骤,突然最后一个步骤异常了,那么其他的前提操作都要消除掉才行,以保证数据的一致性,并且在这种核心业务下,有大量的代码来做善后工作,进行数据补救,所以对于这种情况下需要使用第一种捕获异常的方式。

如果异常不是那么重要,并且在单一入口、MVC风格的应用中,为了保持代码流程的统一,则往往使用后一种异常捕获的方式。

1.4 PHP的错误级别

PHP手册中一共定义了16个级别的错误,不过一般用不到这么多,大致可以分为以下几类:
  • deprecated:最低级别的错误,表示不推荐、不建议,比如说PHP 5中使用ereg函数会报此类错误(注:PHP 7 已经移除该函数,所以PHP7使用ereg会报致命错误),这种错误一般由于使用不推荐、过时的函数或者是语法造成的,虽不影响正常流程,但还是建议修正
  • notice:通知级别的错误,一般是语法存在不当的地方,比如使用未定义的变量,比如数组索引是字符的时候没有加引号等,这种错误不影响正常流程,但是建议修正
  • warning:警告级别的错误,在语法中出现很不恰当的情况下会报此错误,比如函数参数不匹配的时候,这种级别的错误会导致得不到预期的结果,需要修改代码
  • fetal error:致命错误,会直接导致PHP流程的提前终结,后面的代码不会执行了,这种情况必须修改该错误
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值