PHP错误和异常处理详解

前言:
关于PHP的错误和异常我准备用四大块来说明,内容如下:

一、错误与异常的
  1. 区别

异常一般指非语法和编译上的错误,指不符合程序预期,业务与流程上的错误,叫异常。
错误一般指PHP本身的报错,比如语法错误、环境错误等。根据错误类型区分错误级别,并且不能被try-catch捕获。

例:如下是一个PHP代码,其中有两个错误,echo xx,没加引号和分号。

<?php
try
{
    echo xx
}catch (Execption $e)
{
    echo $e->getMessage();
}

// 结果
// Parse error: syntax error, unexpected '}', expecting ',' or ';' in E:\work\app\phptest\index.php on line 5

由此可见PHP遇见本身的错误会直接触发一个错误,而不是异常,通过异常无法自动捕获错误。

二、错误处理

在PHP中的异常机制是不完善的,有些时候必须经过处理才能抛出异常,这样相当麻烦,所以我们可以采用自定义函数来接管PHP原生的报错。

1.错误配置指令

  • 开启PHP错误
    全局:在PHP配置文件中,display_errors = on/off;
    局部:在程序中输入,ini_set("display_error", true/false);
    注:log_errors = On情况下,必须制定error_log文件,如果指定地址错误或没有权限则会导致display_errors = Off失效,错误正常打印出来。

  • 首先先了解下错误等级

级别 描述
E_ALL 所有错误和警告(不包括E_STRICT错误)
E_COMPILE_ERROR 致命的编译错误
E_COMPILE_WARNING 编译时警告
E_CORE_ERROR PHP启动时发生的错误
E_CORE_WARNING PHP启动时的警告
E_DEPRECATED 使用PHP将在未来版本中移除的特性
E_ERROR 致命的运行错误
E_NOTICE 运行时注意的消息
E_PARSE 编译时解析错误
E_RECOVERABLE_ERROR 几近致命的错误
E_STRICT PHP版本可移植性建议
E_USER_DEPRECATED 用户使用PHP将在未来版本中移除的特性
E_USER_ERROR 用户导致的错误
E_USER_NOTICE 用户导致的注意消息
E_USER_WARNING 用户导致的警告
E_WARNING 运行时警告
  • 这些等级的作用
    在开发阶段,希望报告的所有错误。
    在配置文件中,设置error_reporting = E_ALL & E_STRICT,表示显示所有错误。
    error_reporting = E_ERROR | E_PARSE | E_CORE_ERROR,表示只考虑致命的运行时错误、解析错误和核心错误。
    error_reporting = E_ALL & ~ E_USER_WARNING,表示不展示E_USER_WARNING错误。(~表示逻辑操作符NOT)
  • 其他配置
    display_startup_errors = On/Off ,表示显示PHP引擎初始化的所有错误。
    error_log = On/Off,表示记录错误(日志)
    log_errors = string,表示日志存放地址
    log_errors_max_len = int,表示每个日志项的最大长度,以字节为单位,默认1024字节,0表示不指定最大字节
    ignore_repeated_errors = On/Off,表示忽略PHP在同一文件同一行上发生的重复错误消息。
    ignore_repeated_source = On/Off 指令将使PHP忽略不同文件中或同一文件中不同行上发生的重复错误。
    track_errors =On/Off 指令会使PHP在变量$php_errormsg中存储最近发生的错误消息。

2.记录日志
两种方式:(以Windows为例)
第一种记录到文件,
error_log = x:/php/php_errors.log,这种方式安全性较低,被攻击者发现可以轻易的浏览目标路径等。


微信图片_20190816172757.png

第二种方式记录到syslog,一种系统日志工具(Linux是syslog日志工具,Windows是Event Viewer),两者大体相同。
具体操作:
error_log = syslog

echo 1/0;

点击“开始→运行”,输入eventvwr,打开事件查看器。


微信图片_20190816173437.png

Linux日志查看暂不举例。

手动记录到syslog函数

// define_syslog_variables(); // 初始化syslog,5.3之后不需要此函数
openlog("CHP8", LOG_PID, LOG_USER);
syslog(LOG_WARNING, "cha");
closelog();
// CHP8[11876] cha

openlog(iden,option,facility)
iden:每一项开始的表示符。
option:使用哪些日志选项,多个用|区分,比如LOG_ODELAY | LOG_PID
其中:
LOG_CONS,如果写入syslog发生错误,则将输出发送到控制台。
LOG_NDELAY,立即打开与syslog的连接
LOG_ODELAY,直到提交第一条消息打开连接,这是默认值。
LOG_PERROR,将要记录的消息同时输出到syslog和标准错误
LOG_PID,每个消息记录进程ID
facility:记录程序属于哪一类,一般包括LOG_KERN、LOG_USER、LOG_MAIL、LOG_DAEMON、LOG_AUTH、LOG_LPR、LOG_LOCALN(LOG_LOCALN最后的N是从0-7的值),指定LOG_CRON消息将发送到cron日志,一般在crontab中执行PHP使用。通常使用LOG_USER。

syslog(priority, message)
priority:日志优先级,表示严重程度。参数如下:
LOG_EMERG,严重的系统问题,可能预示崩溃。
LOG_ALERT,必须立即解决的情况,可能危害系统完整性。
LOG_CRIT,紧急错误,可能导致服务器不可用。
LOG_ERR,一般错误。
LOG_WARNING,一般警告。
LOG_NOTICE,正常但值得注意的情况。
LOG_INFO,一般信息。
LOG_DEBUG,一般与调试相关信息。
message:错误内容。

三、异常处理
  1. 基本异常使用
try
{
    if (1)
    {
        throw new Exception("index Exception!");
    }

}catch(Exception $e)
{
    echo $e->getFile() . $e->getLine() . $e->getMessage();
}

// E:\work\app\phptest\index.php6index Exception!

其中包括7个方法:
getCode(),返回传递给构造函数的错误代码。
getFile(),抛出异常文件名。
getLine(),抛出异常行号。
getMessage(),返回传递的消息。
getPrevious(),返回前一个异常。
getTrace(),返回一个数组,包括上下文的信息。
getTraceAsString(),同上,不过返回的是字符串。

2.扩展异常类

<?php
class MyException extends Exception
{
    function __construct($language, $errorcode)
    {
        $this->language = $language;
        $this->errorcode = $errorcode;
    }

    function getMessageMap()
    {
        $errors = [
            'en' => ['file error', 'name error'],
            'ch' => ['文件错误', '名字错误'],
        ];

        return $errors[$this->language][$this->errorcode];
    }
}

try
{
    if (1)
    {
        throw new MyException("ch", 1);
    }

}catch(MyException $e)
{
    echo $e->getMessageMap();
}
// 结果
// 名字错误

但是这样的异常处理其实意义不大。

四、错误异常联用
  1. 接管PHP原生异常
    用set_error_handler(error_function, error_type)函数自定义错误处理函数。
<?php
// 第一种
/*class MyErrorClass{
    // 必须是静态
    public static function MyError($number, $message, $file, $line)
    {
        print_r(['code' => $number, 'message' => $message, 'file' => $file, 'line' => $line]);
    } 
}

set_error_handler(['MyErrorClass', 'MyError']);*/

// 第二种
function MyError($number, $message, $file, $line)
{
    print_r(['code' => $number, 'message' => $message, 'file' => $file, 'line' => $line]);
}

set_error_handler('myError');


try {
    $a = 1 / 0;
} catch (Exception $e) 
{
    echo "0不能做被除数";
}

// 结果
// Array ( [code] => 2 [message] => Division by zero [file] => E:\work\app\phptest\index.php [line] => 23 )

由此可知,自定方法截取了错误,此时我们可以操作错误抛出异常。
注意三点:

  • 若用该方法,则error_reporting()不能再使用。
  • 此方法不能处理E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING级别错误,该函数只能捕获系统产生的一些Warning、Notice级别的错误。
  • 在报错前需要先注册本函数。

2.执行结束返回错误数据
register_shutdown_function(exception_function)可捕获Fatal Error、Parse Error等错误。不如脚本错误、die、exit、异常等结束都会调用。通过它可以在脚本结束前发现执行是否有误。利用error_get_last()查看错误。

echo aaa; 
register_shutdown_function('shutdown');
function shutdown()
{
    // error_get_last()获取错误数组
    if ($error = error_get_last()) 
    {
        echo "<pre>";
        print_r($error);
    }
}
/*

Warning: Use of undefined constant aaa - assumed 'aaa' (this will throw an Error in a future version of PHP) in E:\work\app\phptest\index.php on line 8
aaa
Array
(
    [type] => 2
    [message] => Use of undefined constant aaa - assumed 'aaa' (this will throw an Error in a future version of PHP)
    [file] => E:\work\app\phptest\index.php
    [line] => 8
)
*/
  1. 用户自定义异常处理
    set_exception_handler(exception_function)
<?php
function myException($exception) 
{
    echo "异常:" , $exception->getMessage();
}
set_exception_handler('myException');
throw new Exception('aa');
// 异常:aa

完整代码

<?php
class MyException extends Exception
{
    function __construct($language, $errorcode)
    {
        $this->language = $language;
        $this->errorcode = $errorcode;
    }

    function getMessageMap()
    {
        $errors = [
            'en' => ['file error', 'name error'],
            'ch' => ['文件错误', '名字错误'],
        ];

        // 记录日志
        openlog("PHP7.2", LOG_PID, LOG_USER);
        syslog(LOG_CRIT, $errors[$this->language][$this->errorcode]);

        return $errors[$this->language][$this->errorcode];
    }
}

function MyError($number, $message, $file, $line)
{
    $errorArray = ['code' => $number, 'message' => $message, 'file' => $file, 'line' => $line];

    // code==2抛出异常
    if ($errorArray['code'] == 2)
    {
        throw new MyException('ch', 1);
    }
}

set_error_handler('myError');

try {
    $a = 1 / 0;

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

推荐阅读更多精彩内容