14.PHP的错误与异常处理

错误和异常不是一回事儿:错误可能是在开发阶段的一些失误而引起的程序问题;而异常则是项目在运行阶段遇到的一些意外,引起程序不能正常运行。

所以如果开发时遇到了错误,开发人员就必须根据错误提示报告及时排除;如果能考虑到程序在运行时可能遇到的异常,就必须为这种意外编写出另外的一种或几种解决方案。

1.错误处理

PHP程序的错误发生一般归属于下列三个领域:

  • 语法错误
  • 运行时错误
  • 逻辑错误

1.1.错误级别

PHP的错误报告级别:

级别常量 错误报告描述
E_ERROR 致命的运行时错误(它会阻止脚本的执行)
E_WARNING 运行时警告(非致命的错误)
E_PARSE 从语法中解析错误
E_NOTICE 运行时注意消息(可能是或者可能不是一个问题)
E_CORE_ERROR 类似E_ERROR,但不包括PHP核心造成的错误
E_CORE_WARNING 类似E_WARNING,但不包括PHP核心错误警告
E_COMPILE_ERROR 致命的编译时错误
E_COMPILE_WARNING 致命的编译时警告
E_USER_ERROR 用户导致的错误消息
E_USER_WARNING 用户导致的警告
E_USER_NOTICE 用户导致的注意消息
E_ALL 所有的错误、警告和注意
E_STRICT 关于PHP版本移植的兼容性和互操作性建议

1.2.设置错误级别

开发人员在开发站点时,会希望PHP报告特定类型的错误,可以通过调整错误报告的级别来实现。可以通过以下两种方法设置错误报告级别:

1.2.1.通过配置文件设置错误级别

可以通过在配置文件php.ini中修改配置指令error_reporting的值,修改成功后重新启动Web服务器,则每个PHP脚本都可以按调整后的错误级别输出错误报告。

1.2.2.通过error_reporting()函数设置错误级别

1.3.错误配置详解

配置指令 描述 默认值
display_error 该选项设置是否将错误信息作为输出的一部分显示到屏幕,或者对用户隐藏而不显示。
error_reporting 设置错误报告的级别。<br />该参数可以是一个任意的表示二进制位字段的整数,或者常数名称。错误级别和常数是在 预定义常量定义的,在 php.ini 之中也有专门的说明。在程序运行时,还可以通过 error_reporting() 函数进行设置。
display_startup_errors 是否显示PHP引擎在初始化时遇到的所有错误 Off
log_errors 确定日志语句记录的位置 Off
error_log 设置错误可以发送到syslog中 Null
log_errors_max_len 每个日志项的最大长度,以字节为单位,设置为0表示指定最大长度 1024
ignore_repeated_errors 是否忽略同一文件、同一行发生的重复错误消息 Off
ignore_repeated_source 忽略不同文件中或同一文件中不同行上发生的重复错误 Off
track_errors 启动该指令会使PHP在$php_errormsg中存储最近发生的错误信息 Off

1.4.使用trigger_error()函数代替die()函数

函数die()等同于exit(),二者如果执行都会终止PHP程序,而且可以在退出程序之前输出一些错误报告。

函数trigger_error()则可以生成一个用户警告来代替,使程序更具有灵活性。

例如,trigger_error("没有找到文件", E_USER_ERROR)。使用trigger_error()函数来代替die()函数,代码在处理错误上会更具优势,对于客户程序员来说更易于处理错误。

1.5. 自定义错误处理

自定义错误报告的处理方式,可以完全绕过标准的PHP错误处理函数,这样就可以按自己定义的格式打印错误报告,或改变错误报告打印的位置(标准PHP的错误报告是哪里发生错误就在发生位置处显示)。

以下几种情况可以考虑自定义错误处理:

  • 可以记下错误的信息,及时发现一些生产环境出现的问题。
  • 可以用来屏蔽错误。出现错误会把一些信息暴露给用户,极有可能成为黑客攻击网站的工具。
  • 可以做相应的处理,将所有错误报告放到脚本最后输出,或出错时可以显示跳转到预先定义好的出错页面,提供更好的用户体验。如果必要,还可以在自定义的错误处理程序中,根据情况终止脚本运行。
  • 可以作为调试工具,有些时候必须在运行环境时调试一些东西,但又不想影响正在使用的用户。
set_error_handler()设置自定义错误处理函数

函数详解:https://www.php.net/manual/zh/function.set-error-handler.php

实例代码参考TP5中的自定义错误处理函数。

函数注意事项:

  • E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING是不会被这个句柄处理的,也就是会用最原始的方式显示出来。不过出现这些错误都是编译或PHP内核出错,在通常情况下不会发生。
  • 使用set_error_handler()函数后,error_reporting()函数将会失效。也就是所有的错误(除上述的错误)都会交给用户自定义的函数处理。

1.6.写错误日志

对于PHP开发者来说,一旦某个产品投入使用,应该立即将display_errors选项关闭,以免因为这些错误所透露的路径、数据库连接、数据表等信息而遭到黑客攻击。但是,任何一个产品在投入使用后,难免会有错误出现,那么如何记录一些对开发者有用的错误报告呢?我们可以在单独的文本文件中将错误报告作为日志记录。错误日志的记录可以帮助开发人员或者管理人员查看系统是否存在问题。

如果需要将程序中的错误报告写入错误日志中,只要在PHP的配置文件中,将配置指令log_errors开启即可。错误报告默认就会记录到Web服务器的日志文件里,例如记录到Apache服务器的错误日志文件error.log中。

当然也可以记录错误日志到指定的文件中或发送给系统syslog。

(1) 使用指定的文件记录错误报告日志

如果使用自己指定的文件记录错误日志,一定要确保将这个文件存放在文档根目录之外,以减少遭到攻击的可能。并且该文件一定要让PHP脚本的执行用户(Web服务器进程所有者)具有写权限。假设在Linux操作系统中,将/usr/local/目录下的error.log文件作为错误日志文件,并设置Web服务器进程用户具有写的权限。然后在PHP的配置文件中,将error_log指令的值设置为这个错误日志文件的绝对路径。需要对php.ini文件中的配置指令做如下修改:

[图片上传失败...(image-8909-1575726091550)]

PHP的配置文件按上面的方式设置完成以后,重新启动Web服务器。这样,在执行PHP的任何脚本文件时,所产生的所有错误报告都不会在浏览器中显示,而会记录在自己指定的错误日志/usr/local/error.log中。此外,不仅可以记录满足error_reporting所定义规则的所有错误,而且还可以使用PHP中的error_log()函数,送出一个用户自定义的错误信息。

error_log()函数详解:https://www.php.net/manual/zh/function.set-error-handler.php

(2) 错误信息记录到操作系统的日志中

1.7.位运算

位运算符:

符号 作用 举例 个人理解
& 按位与 m &n 全1为1,否则为0
| 按位或 m |n 全0为0,有1为1
^ 按位异或 m ^n 不同为1,相同为0
~ 按位取反 ~$m 1变0,0变1
<< 向左位移 m <<n
>> 向右位移 m >>n

位运算是针对字节(每字节有8个位)上的位来进行运算。

把位的0/1当成假/真,针对每个位上的逻辑运算,就算是位运算。

/*
10进制            2进制
5               0000 0101
12              0000 1100
&               ---------------
                0000 0100
|               ---------------
                0000 1101
^               --------------- 
                0000 1001 
*/  

echo 5 & 12,"<br/>";  //4

echo 5 | 12,"<br/>";  //13

echo 5 ^ 12,"<br/>";  //9

echo ~5,"<br/>";      //-6  

// 利用位运算的左移,使整数增大2倍
$a = 3;
echo $a << 1,"<br/>"; //6
echo $a << 2,"<br/>"; //12

// 右移
$a = 3;
echo $a >> 1,"<br/>"; //1

$a = 8;
echo $a >> 2,"<br/>"; //2

1.8.位运算与错误报告设置

通过查看PHP错误预定义常量,PHP把不同等级的错误级别用十进制数字表示。

如果把十进制转为二级制,会发现有如下特点:

1:0000 0001
2:0000 0010
3:0000 0100
4:0000 1000
5:0001 0000

以上的特点就是一个错误只会有一个1。

如果两个错误,就会有两个一。

如果所有错误了?

1111 1111

就全部都是1。

设置错误范例:

// 设置报告全部错误
error_reporting(E_ALL);

// 禁止报告错误
// 第一种
error_reporting(0);

// 第二种
error_reporting(-1);

// 报告所有错误,不包括E_NOTICE
// 第一种
error_reporting(E_ALL ^ E_NOTICE);

// 第二种
error_reporting(E_ALL & ~E_NOTICE);

2.异常处理

一个异常(Exception)则是在一个程序执行过程中出现的一个例外或是一个事件,它中断了正常指令的运行,跳转到其他程序模块继续执行。所以异常处理经常被当作程序的控制流程使用。

无论是错误还是异常,应用程序都必须能够以妥善的方式处理,并作出相应的反应,希望不要丢失数据或者导致程序崩溃。异常处理用于在指定的错误发生时改变脚本的正常流程,是PHP 5中一个新的重要特性。

异常处理是一种可扩展、易维护的错误处理统一机制,并提供了一种新的面向对象的错误处理方式。

在Java、C#及Python等语言中很早就提供了这种异常处理机制,如果你熟悉某一种语言中的异常处理,那么对PHP中提供的异常处理机制也不会陌生。

异常是错误产生后生成的对象。

异常是你预知可能会产生的一个行为,并对该行为作出主动处理(throw)的流程。但也有例外,比如实例化PDO对象时,可以被动触发异常。

异常与错误的区别:

  • 异常可以向上传递,错误不可以。
  • 异常可以在运行时进行修正处理,错误必须停下来修改代码,修改好后再次运行排除错误。

2.1.异常处理实现

异常执行流程:

try{
    // 需要异常处理的代码段
    throw 语句抛出异常;
    // 异常抛出后,在try代码块并在throw语句之后的代码不会再执行。
}catch(Exception $e){
    // 异常处理
    // 可以通过`die()`函数停止执行之后的所有代码
}catch(Exception $e){
    // 异常处理
}
// 异常处理后继续执行之后代码

在PHP代码中所产生的异常可以被throw语句抛出并被catch语句捕获。需要进行异常处理的代码都必须放入try代码块内,以便捕获可能存在的异常。每一个try至少要有一个与之对应的catch,也不能出现单独的catch。另外,try和cache之间也不能有任何的代码出现。一个异常处理的简单实例如下所示:

[图片上传失败...(image-42c79c-1575726091550)]

在PHP中,异常必须手动抛出。throw关键字将触发异常处理机制,它是一个语言结构,而不是一个函数,但必须给它传递一个对象作为值。

在最简单的情况下,可以实例化一个内置的Exception类,就像以上代码所示那样。如果在try语句中有异常对象被抛出,该代码块不会再继续向下执行,而直接跳转到catch中执行。并传递给catch代码块一个对象,也可以理解为被catch代码块捕获的对象,其实就是导致异常被throw语句抛出的对象。在catch代码块中可以简单地输出

一些异常的原因,也可以是try代码块中任务的另一个版本解决方案。此外,也可以在这个catch代码块中产生新的异常。最重要的是,在异常处理之后,程序不会崩溃,而会继续执行。

2.2. 扩展PHP内置的异常处理类

在try代码块中,需要使用throw语句抛出一个异常对象,才能跳转到catch代码块中执行,并在catch代码块中捕获并使用这个异常类的对象。虽然在PHP中提供的内置异常处理类Exception已经具有非常不错的特性,但在某些情况下,可能还要扩展这个类来得到更多的功能。所以用户可以用自定义的异常处理类来扩展PHP内置的异常处理类。以下代码说明了在内置的异常处理类中,哪些属性和方法在子类中是可访问和可继承的:

Exception {

    /* 属性 */

    protected string $message;

    protected int $code;

    protected string $file;

    protected int $line;

    /* 方法 */

    public __construct([ string $message = ""[, int $code = 0[, Exception $previous = NULL]]] )

    final public string getMessage( void)

    final public Exception getPrevious( void)

    final public int getCode( void)

    final public string getFile( void)

    final public int getLine( void)

    final public array getTrace( void)

    final public string getTraceAsString( void)

    public string __toString( void)

    final private void __clone( void)
}
  • 如果使用自定义的类作为异常处理类,则必须是扩展内置异常处理类Exception的子类,非Exception类的子类是不能作为异常处理类使用的。
  • 如果在扩展内置处理类Exception时重新定义构造函数,建议同时调用parent::construct()来检查所有的变量是否已被赋值。
  • 当对象要输出字符串的时候,可以重载__toString()并自定义输出的样式。
  • 可以在自定义的子类中,直接使用内置异常处理类Exception中的所有成员属性,但不能重新改写从该父类中继承过来的成员方法,因为该类的大多数公有方法都是final的。

创建一个自定义的MyException类,继承了内置异常处理类Exception中的所有属性,并向其添加了自定义的方法。代码及应用如下所示:

/* 自定义的一个异常处理类,但必须是扩展内异常处理类的子类 */
class MyException extends Exception{
    //重定义构造器使第一个参数 message 变为必须被指定的属性
    public function __construct($message, $code=0){
        //可以在这里定义一些自己的代码
        //建议同时调用 parent::construct()来检查所有的变量是否已被赋值
        parent::__construct($message, $code);
    }
    
    public function __toString() {        //重写父类方法,自定义字符串输出的样式
        return __CLASS__.":[".$this->code."]:".$this->message."<br>";
    }
    
    public function customFunction() {    //为这个异常自定义一个处理方法
        echo "按自定义的方法处理出现的这个类型的异常<br>";
    }
}

try {                                //使用自定义的异常类捕获一个异常,并处理异常
    $error = '允许抛出这个错误';       
    throw new MyException($error);    //创建一个自定义的异常类对象,通过throw语句抛出
    echo 'Never executed';             //从这里开始,try代码块内的代码将不会再被执行
} catch (MyException $e) {             //捕获自定义的异常对象
    echo '捕获异常: '.$e;              //输出捕获的异常消息
    $e->customFunction();            //通过自定义的异常对象中的方法处理异常
}
echo '你好呀';                        //程序没有崩溃继续向下执行

2.3.捕获多个异常

在try代码块之后,可以给出一个catch代码块,也可以是多个catch代码块。

如果每个catch代码块可以捕获一个不同类型的异常,那么使用多个catch就可以捕获不同的类所产生的异常。

当产生一个异常时,PHP将查询一个匹配的catch代码块。如果有多个catch代码块,传递给每一个catch代码块的对象必须具有不同的类型,这样PHP可以找到需要进入哪一个catch代码块。当try代码块不再抛出异常或者找不到catch能匹配所抛出的异常时,PHP代码就会跳转到最后一个catch的后面继续执行。

多个异常捕获的示例代码如下:

/* 自定义的一个异常处理类,但必须是扩展内异常处理类的子类 */
class MyException extends Exception{
    //重定义构造器使第一个参数 message 变为必须被指定的属性
    public function __construct($message, $code=0){
        //可以在这里定义一些自己的代码
        //建议同时调用 parent::construct()来检查所有的变量是否已被赋值
        parent::__construct($message, $code);
    }
    //重写父类中继承过来的方法,自定义字符串输出的样式
    public function __toString() {
        return __CLASS__.":[".$this->code."]:".$this->message."<br>";
    }

    //为这个异常自定义一个处理方法
    public function customFunction() {
        echo "按自定义的方法处理出现的这个类型的异常";
    }
}

/* 创建一个用于测试自定义扩展的异常类MyException */
class TestException {
    public $var;                      //一个成员属性,用来判断对象是否创建成功被初使化

    function __construct($value=0) {     //通过构造方法的传值决定抛出的异常
        switch($value){              //对传入的值进行选择性的判断
            case 1:                 //如果传入的参数值为1,则抛出自定义的异常对象
                throw new MyException("传入的值“1” 是一个无效的参数", 5);
                break;
            case 2:                //如果传入的参数值为2,则抛出PHP内置的异常对象
                throw new Exception("传入的值“2”不允许作为一个参数", 6);
                break;
            default:                //如果传入的参数值合法,则不抛出异常创建对象成功 
                $this->var=$value;  //为对象中的成员属性赋值
                break;
        }
    }
}
//示例1,在没有异常时,程序正常执行,try中的代码全部执行并不会执行任何catch区块
try{
    $testObj=new TestException();           //使用默认参数创建异常的测试类对象
    echo "***********<br>";               //没有抛出异常这条语句就会正常执行
}catch(MyException $e){                    //捕获用户自定义的异常区块
    echo "捕获自定义的异常:$e <br>";     //按自定义的方式输出异常消息
    $e->customFunction();                 //可以调用自定义的异常处理方法
}catch(Exception $e) {                      //捕获PHP内置的异常处理类的对象
    echo "捕获默认的异常:".$e->getMessage()."<br>";   //输出异常消息
}   
var_dump($testObj);          //判断对象是否创建成功,如果没有任何异常,则创建成功

//示例2,抛出自定义的异常,并通过自定义的异常处理类捕获这个异常并处理
try{
    $testObj1=new TestException(1);         //传入参数1时,创建测试类对象抛出自定义异常
    echo "***********<br>";               //这个语句不会被执行
}catch(MyException $e){                    //这个catch区块中的代码将被执行
    echo "捕获自定义的异常:$e <br>";
    $e->customFunction();
}catch(Exception $e) {                      //这个catch区块不会执行
    echo "捕获默认的异常:".$e->getMessage()."<br>";
}   
var_dump($testObj1);                      //有异常产生,这个对象没有创建成功

//示例2,抛出内置的异常,并通过自定义的异常处理类捕获这个异常并处理
try{
    $testObj2=new TestException(2);        //传入参数2时,创建测试类对象抛出内置异常
    echo "***********<br>";             //这个语句不会被执行
}catch(MyException $e){                  //这个catch区块不会执行
    echo "捕获自定义的异常:$e <br>";
    $e->customFunction();
}catch(Exception $e) {                    //这个catch区块中的代码将被执行
    echo "捕获默认的异常:".$e->getMessage()."<br>";
}   
var_dump($testObj2);                    //有异常产生,这个对象没有创建成功

在上面的代码中,可以使用两个异常处理类:

一个是自定义的异常处理类MyException;另一个则是PHP中内置的异常处理类Exception。

分别在try区块中创建测试类TestException的对象,并根据构造方法中提供的不同数字参数,抛出自定义异常类对象、内置的异常类对象和不抛出任何异常的情况,跳转到对应的catch区块中执行。如果没有异常发生,则不会进入任何一个catch块中执行,测试类TestException的对象创建成功。

3.小结

本章必须掌握的知识点

  • 修改错误报告级别。
  • 写错误日志。
  • 异常处理实现。
  • 扩展PHP内置的异常处理类。
  • 捕获多个异常。

本章需要了解的内容

  • 了解错误报告级别类型。
  • 自定义错误报告处理。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容

  •   由于 JavaScript 本身是动态语言,而且多年来一直没有固定的开发工具,因此人们普遍认为它是一种最难于调...
    霜天晓阅读 757评论 0 1
  • error code(错误代码)=0是操作成功完成。error code(错误代码)=1是功能错误。error c...
    Heikki_阅读 3,350评论 1 9
  • 异常与错误 异常是指程序运行中不符合预期情况以及与正常流程不同的状况。错误则属于自身问题,是一种非法语法或者环境问...
    单板小智阅读 1,661评论 0 5
  • 异常与错误异常是指程序运行中不符合预期情况以及与正常流程不同的状况。错误则属于自身问题,是一种非法语法或者环境问题...
    riyihu阅读 313评论 1 1
  • 先聊一聊 前几天在做一个用到file_get_contents去定时提取改退签规则和免费行李额的小需求,每天抓一次...
    孙小胖2018阅读 633评论 0 1