前言:
关于PHP的错误和异常我准备用四大块来说明,内容如下:
一、错误与异常的
区别
异常一般指非语法和编译上的错误,指不符合程序预期,业务与流程上的错误,叫异常。
错误一般指PHP本身的报错,比如语法错误、环境错误等。根据错误类型区分错误级别,并且不能被try-catch捕获。
例:如下是一个PHP代码,其中有两个错误,echo xx,没加引号和分号。
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:错误内容。
三、异常处理
基本异常使用
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.扩展异常类
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();
}
// 结果
// 名字错误
但是这样的异常处理其实意义不大。
四、错误异常联用
接管PHP原生异常
用set_error_handler(error_function, error_type)函数自定义错误处理函数。
// 第一种
/*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 "
";
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
)
*/
用户自定义异常处理
set_exception_handler(exception_function)
function myException($exception)
{
echo "异常:" , $exception->getMessage();
}
set_exception_handler('myException');
throw new Exception('aa');
// 异常:aa
完整代码
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();
}