1、类的错误处理、异常处理

PHP提供了一个Exception基类,用于监听PHP的错误和异常提示,而我们只需要集成该类,并重写其中的一些成员函数,就可以编写出一个漂亮的错误提示页面。

A、什么是错误级别?

PHP在运行时, 针对严重程度不同的错误(级别),会报出不同的错误警告提示。
我们在开发中, 为了程序的规范性,一般会把报错级别调到最高,有助于我们快速定位错误和代码规范,但是,在产品上线后,网站运营过程中,就不宜报这么多错。
原因有以下几个:

  1. 1、各种错误提示会给用户带来不好的印象
  2. 2、在报错时,把网站的绝对路径,文件路径,如D:\www\1015都报出来了,会大大增加被攻击的风险

因此,在网站上线后,就应该让报错级别降低,少报错甚至不报。
下面,我们来看下PHP具体都有哪些报错级别:

常量 描述
2 E_WARNING 非致命的 run-time 错误。不暂停脚本执行。
8 E_NOTICE Run-time 通知。脚本发现可能有错误发生,但也可能在脚本正常运行时发生。
256 E_USER_ERROR 致命的用户生成的错误。这类似于程序员使用 PHP 函数 trigger_error() 设置的 E_ERROR。
512 E_USER_WARNING 非致命的用户生成的警告。这类似于程序员使用 PHP 函数 trigger_error() 设置的 E_WARNING。
1024 E_USER_NOTICE 用户生成的通知。这类似于程序员使用 PHP 函数 trigger_error() 设置的 E_NOTICE。
4096 E_RECOVERABLE_ERROR 可捕获的致命错误。类似 E_ERROR,但可被用户定义的处理程序捕获。(参见 set_error_handler())
8191 E_ALL 所有错误和警告,除级别 E_STRICT 以外。(在 PHP 6.0,E_STRICT 是 E_ALL 的一部分)

PHP修改报错级别,主要有以下两种方式:

1、php.ini里修改error_reporting 选项
2、也可以在php代码里,使用error_reporting()函数来修改。该函数可以传上列表格中的值或常量参数。

B、PHP面向过程 - 异常监听

PHP中提供了异常监听,可以使用try{}catch(){}语句去监听报错信息,
注意,正常的逻辑代码,需要放在try{}模块里执行,然后使用throw new Exception()抛出异常。
示例代码如下:

<?php
$num = 1;
try {
    # 我是正常的逻辑代码
    if ($num != 1) {
        # 这里抛出异常,下面接住
        throw new Exception("你抛出了一个错误");
    }
} catch(Exception $e) {
    # 我这里抓住,然后打印
    echo 'Message: ' .$e->getMessage();
}

下面对三个子模块进行详细讲解:

1、try - 使用异常的函数应该位于 "try" 代码块内。如果没有触发异常,则代码将照常继续执行。但是如果异常被触发,会抛出一个异常;
2、throw - 这里规定如何触发异常。每一个 "throw" 必须对应至少一个 "catch";
3、catch - "catch" 代码块会捕获异常,并创建一个包含异常信息的对象;

上面的例子解释:

1、在 "try" 代码块中检测$num是否等于 1,如果不否,则抛出一个异常。
2、throw new Exception()抛出异常。
3、"catch" 代码块接收到该异常,并创建一个包含异常信息的对象 ($e)。
4、通过从这个 exception 对象调用 $e->getMessage(),输出来自该异常的错误消息。

不过值得注意的是,try{}catch(){}只能捕抓到异常信息,而无法捕抓到PHP的错误信息,而PHP中,异常与错误有什么区别,接下来我们需要详细的了解下:
什么是异常:
程序在运行中出现不符合预期的情况,允许发生(你也不想让他出现不正常的情况)但他是一种不正常的情况,
按照我们的正常逻辑本不该出的错误,但仍然会出现的错误,属于逻辑和业务流程的错误,而不是编译或者语法上的错误
出现异常的情况下,PHP代码仍可以往下继续执行。
什么是错误:
属于php脚本自身的问题,大部分情况是由错误的语法,服务器环境导致,使得编译器无法通过检查,甚至无法运行的情况。
warning、notice都是错误,只是他们的级别不同而已,并且错误是不能被try{}catch(){}语句捕获的。
上面的说法是有前提条件的:
在PHP中,因为在其他语言中就不能这样下结论了,也就是说异常和错误的说法在不同的语言有不同的说法。
在PHP中任何自身的错误或者是非正常的代码都会当做错误对待,并不会以异常的形式抛出,
但是也有一些情况会当做异常和错误同时抛出(据说是,我没有找到合适的例子)。
也就是说,你想在数据库连接失败的时候自动捕获异常是行不通的,因为这就不是异常,是错误。
但是在java中就不一样了,他会把很多和预期不一致的行为当做异常来进行捕获。
PHP异常处理很鸡肋?
在上面的分析中我们可以看出,PHP并不能主动的抛出异常,但是你可以手动抛出异常,这就很无语了,如果你知道哪里会出问题,你添加if else解决不就行了吗,为啥还要手动抛出异常,既然能手动抛出就证明这个不是异常,而是意料之中。
以我的理解,这就是PHP异常处理鸡肋的地方(不一定对啊)。
所以PHP的异常机制不是那么的完美,但是使用过MVC框架的朋友都知道有这个情况:
就是你框架中编写任何错误代码时,它都能捕捉到错误或异常信息,然后转换成一个美化的提示页面。这是为什么?
看过源码的朋友都知道这些MVC框架中,都对PHP的异常和错误机制进行了重写,通过重写PHP系统提供的Exception类,可以实现自定义的错误异常机制,美化提示界面。
下面我们就来学习下,PHP面向对象的错误异常处理。

C、PHP面向对象 - 错误异常处理

在上面PHP面向过程,对错误异常捕捉的课件中,我们有了解到是依赖一个Exception类来进行错误信息捕捉,而在面向对象中,这个Exception类是支持继承重写的。
异常处理类:Exception是所有异常处理的基类,由PHP系统自带提供,具有以下几个属性和方法,其中包括了:
常用的成员属性有:

message 异常消息内容
code 异常代码
file 抛出异常的文件名
line 抛出异常在该文件的行数

常用的方法有:

getTrace() 获取异常追踪信息
getTraceAsString() 获取异常追踪信息的字符串
getMessage() 获取出错信息

在有的时候为了美化报错提示界面(很多系统或框架中),都会通过继承Exception类来建立自定义的异常处理类。

<?php
#自定义的异常类,继承了PHP的异常基类Exception
class MyException extends Exception {
    function getInfo() {
        return '自定义错误信息';
    }
}
try {
    # 使用异常的函数应该位于 "try"  代码块内。如果没有触发异常,则代码将照常继续执行。但是如果异常被触发,会抛出一个异常。
    # 这里规定如何触发异常。注意:每一个 "throw" 必须对应至少一个 "catch",当然可以对应多个"catch"
    throw new MyException('error');
} catch(Exception $e)

在了解了异常处理的基本原理之后,我们可以通过try catch来捕获异常,我们将执行的代码放在try代码块中,一旦其中的代码抛出异常,就能在catch中捕获。
这里我们只是通过案例来了解try{}catch(){}的机制以及异常捕获的方法,在实际应用中,不会轻易的抛出异常,
只有在极端情况或者非常重要的情况下,才会抛出异常,抛出异常,可以保障程序的正确性与安全,避免导致不可预知的bug。
一般的异常处理流程代码为:

<?php
try {
    throw new Exception('wrong');
} catch(Exception $ex) {
    echo 'Error:'.$ex->getMessage().'<br>';
    echo $ex->getTraceAsString().'<br>';
}
echo '异常处理后,继续执行其他代码';

在异常被捕获之后,我们可以通过异常处理对象获取其中的异常信息,前面我们已经了解捕获方式,以及获取基本的错误信息。
在实际应用中,我们通常会获取足够多的异常信息,然后写入到错误日志中。
通过我们需要将报错的文件名、行号、错误信息、异常追踪信息等记录到日志中,以便调试与修复问题。

<?php
try {
    throw new Exception('wrong');
} catch(Exception $ex) {
    $msg = 'Error:'.$ex->getMessage()."\n";
    $msg.= $ex->getTraceAsString()."\n";
    $msg.= '异常行号:'.$ex->getLine()."\n";
    $msg.= '所在文件:'.$ex->getFile()."\n";
    # 将异常信息记录到日志中
    file_put_contents('error.log', $msg);
}

D、自定义错误异常处理

在某些时候,我们不想要继承Exception基类,而是自己编写一个独立的class类由于截取系统错误信息,
PHP5+之后,提供了一系列的错误行为扩展函数,用于支持实现这样的业务场景,官方手册: http://php.net/manual/zh/book.errorfunc.php。
下面,我们来学习下set_error_handler()函数,该函数主要用于设置用户自定义的错误处理函数。
该函数主要依次返回以下5个参数:

1、errno,包含了错误的级别,是一个 integer。
2、errstr,包含了错误的信息,是一个 string。
3、errfile, 包含了发生错误的文件名,是一个 string。
4、errline, 包含了错误发生的行号,是一个 integer。
5、errcontext, 是一个指向错误发生时活动符号表的array。

也就是说,errcontext 会包含错误触发处作用域内所有变量的数组。
下面我们来学习简单的使用方法:

<?php
class Log{
    /**
     * 应用程序初始化
     */
    public function __construct() {
        set_error_handler('Log::appError');
    }
    /**
     * 自定义错误处理
     * @access public
     * @param int $errno 错误类型
     * @param string $errstr 错误信息
     * @param string $errfile 错误文件
     * @param int $errline 错误行数
     * @param int $errcontext 错误上下文
     * @return void
     */
    static public function appError($errno, $errstr, $errfile, $errline, $errcontext) {
        switch ($errno) {
            case E_ERROR:
            case E_PARSE:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_USER_ERROR:
                ob_end_clean();
                $errorStr = "$errstr ".$errfile." 第 $errline 行.";
                break;
            default:
                $errorStr = "[$errno] $errstr ".$errfile." 第 $errline 行.";
                break;
        }
        echo $errorStr;
        exit;
    }
}
# 使用Demo如下:
new Log();
echo $asd;