先聊一聊
前几天在做一个用到file_get_contents去定时提取改退签规则和免费行李额的小需求,每天抓一次。本来运行一切正常,但是在运行了大概一周左右,完了,估计东航知道我每天都去抓他的网页了,可是竟然不让我继续抓了,估计东航对我的ip做了限制,直接导致我用file_get_contents报错,老板就问我,怎么回事啊~你看报错邮件都发出来了,你得赶快解决一下,由此我决定做个异常处理。
PHP错误和异常的区别
然而我们都知道file_get_contents抓不到网页内容是报错啊,实打实的报错啊,异常是什么,异常通常是可预见的逻辑问题,不是报错,这里我得强调一下,报错和异常完全是两码事。
PHP中将代码自身异常(一般是环境或者语法非法所致)成为错误,
将运行中出现的逻辑错误称为异常(Exception)
错误是没法通过代码处理的,
而异常则可以通过try/catch处理.
报错真的是报错,默认情况下是不能用try{}catch{}捕获的。下面是一个典型报错。
PHP异常的处理
对于PHP异常,我们通常会采用try{}catch{}去处理异常
try{
if($a>10){
throw new Exception('规定不能大于10哦~');
}
}catch(Exception $e){
echo $e->getMessage();
}
主要是使用throw去抛出异常,用caych代码块捕获异常,这样我们就可以很好的去处理异常了。
那对于错误怎么处理呢,如果让PHP不报错,而是走异常被catch代码块捕捉,这样我们就可以自己去处理错误了
这就得清楚下面几个大神函数了
set_error_handler()
听名字就知道,这个函数是用来处理错误的。用法如下
<?php
set_error_handler("getError");
function getError($type, $message, $file, $line)
{
$error = $type."--".$message.'--'.$file.'--'.$line;
throw new Exception($error);
}
try{
file_get_contents('xxoo.php');
}catch(Exception $e)
{
//处理自己的异常业务逻辑
echo $e->getMessage();
}
//输出
2--file_get_contents(xxoo.php): failed to open stream: No such file or directory--D:\wamp64\www\shejuyi\index.php--19
?>
解释一下
file_get_contents()抓取一个内容,当内容不存在时会报错,自动调用我们set_error_handler设置的函数getError(),在getError()中我们抛出了一个异常,所以会被catch代码块捕获。
不过需要注意一下两点:第一,如果存在该方法,相应的error_reporting()就不能在使用了。所有的错误都会交给自定义的函数处理。第二,此方法不能处理以下级别的错误:E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,set_error_handler() 函数所在文件中产生的E_STRICT,该函数只能捕获系统产生的一些Warning、Notice级别的错误。
主要调用方式
<?php
// 直接传函数名 NonClassFunction
set_error_handler('function_name');
// 传 class_name && function_name
set_error_handler(array('class_name', 'function_name'));
?>
register_shutdown_function()
捕获PHP的错误:Fatal Error、Parse Error等,这个方法是PHP脚本执行结束前最后一个调用的函数,比如脚本错误、die()、exit、异常、正常结束都会调用,多么牛逼的一个函数啊!通过这个函数就可以在脚本结束前判断这次执行是否有错误产生,这时就要借助于一个函数:error_get_last();这个函数可以拿到本次执行产生的所有错误。error_get_last();返回的信息:
[type] - 错误类型
[message] - 错误消息
[file] - 发生错误所在的文件
[line] - 发生错误所在的行
<?php
register_shutdown_function('getError');
function getError()
{
if ($error = error_get_last()) {
var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
}
}
?>
通过这种方法就可以巧妙的打印出程序结束前所有的错误信息。但是我在测试的时候我发现并不是所有的错误终止后都会调用这个函数,可以看下面的一个测试文件,内容是:
<?php
register_shutdown_function('zyfshutdownfunc');
function zyfshutdownfunc()
{
if ($error = error_get_last()) {
var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
}
}
var_dump(23+-+); //此处语法错误
?>
//结果
Parse error: syntax error, unexpected ')' in /www/mytest/exception/try-catch.php on line 71
由此引出一个奇葩的问题:问什么不能触发,为什么框架中是可以的?其实原因很简单,只在parse-time出错时是不会调用本函数的。只有在run-time出错的时候,才会调用本函数,我的理解是语法检查器前没有执行register_shutdown_function()去把需要注册的函数放到调用的堆栈中,所以就根本不会运行。那框架中为什么任何错误都能进入到register_shutdown_function()中呢,其实在框架中一般会有统一的入口index.php,然后每个类库文件都会通过include ** 的方式加载到index.php中,相当与所有的程序都会在index.php中聚集,同样,你写的具有语法错误的文件也会被引入到入口文件中,这样的话,调用框架,执行index.php,index.php本身并没有语法错误,也就不会产生parse-time错误,而是 include 文件出错了,是run-time的时候出错了,所以框架执行完之后就会触发register_shutdown_function();
a.php文件
<?php
// 模拟语法错误
var_dump(23+-+);
?>
b.php文件
<?php
register_shutdown_function('zyfshutdownfunc');
function zyfshutdownfunc()
{
if ($error = error_get_last()) {
var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
}
}
require 'a.php';
?>
set_exception_handler()
当一个异常被抛出后代码会立即停止执行,其后的代码将不会继续执行,PHP 会尝试查找匹配的 "catch" 代码块。如果一个异常没有被捕获,而且又没用使用set_exception_handler()作相应的处理的话,那么 PHP 将会产生一个严重的错误,并且输出未能捕获异常(Uncaught Exception ...)的提示信息。
设置默认的异常处理程序,用在没有用try/catch块来捕获的异常,也就是说不管你抛出的异常有没有人捕获,如果没有人捕获就会进入到该方法中,并且在回调函数调用后异常会中止。看一下用法:
<?php
set_exception_handler('getException');
function getException($exception)
{
var_dump("<b>set_exception_handler: Exception: " . $exception->getMessage() . '</b>');
}
throw new Exception("zyf exception");
?>
捕获所有错误
由set_error_handler()可知,他能够捕获一部分错误,不能捕获系统级E_ERROR、E_PARSE等错误,但是这部分可以由register_shutdown_function()捕获。所以两者结合能出现很好的功能。
a.php内容:
<?
// 模拟Fatal error错误
//test();
// 模拟用户产生ERROR错误
//trigger_error('zyf-error', E_USER_ERROR);
// 模拟语法错误
var_dump(23+-+);
// 模拟Notice错误
//echo $f;
// 模拟Warning错误
//echo '123';
//ob_flush();
//flush();
//header("Content-type:text/html;charset=gb2312");
?>
b.php内容:
<?
error_reporting(0);
register_shutdown_function('zyfshutdownfunc');
function zyfshutdownfunc()
{
if ($error = error_get_last()) {
var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
}
}
set_error_handler('zyferror');
function zyferror($type, $message, $file, $line)
{
var_dump('<b>set_error_handler: ' . $type . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .</b><br />');
}
require 'a.php';
?>
自定义异常处理和异常嵌套
在复杂的系统中,我们往往需要自己捕获我们需要特殊处理的异常,这些异常可能是特殊情况下抛出的。所以我们就自己定义一个异常捕获类,该类必须是 exception 类的一个扩展,该类继承了 PHP 的 exception 类的所有属性,并且我们可以添加自定义的函数,使用的时候其实和之前的一样,大致写法如下:
<?php
class zyfException extends Exception
{
public function errorzyfMessage()
{
return 'Error line ' . $this->getLine().' in ' . $this->getFile()
.': <b>' . $this->getMessage() . '</b> Must in (0 - 60)';
}
}
$age = 10;
try {
$age = intval($age);
if($age > 60) {
throw new zyfException($age);
}
} catch (zyfException $e) {
echo $e->errorzyfMessage();
}
?>
异常嵌套
在catch中再抛出异常给上层:
<?php
class zyfException extends Exception
{
public function errorzyfMessage()
{
return 'Error line ' . $this->getLine().' in ' . $this->getFile()
.': <b>' . $this->getMessage() . '</b> Must in (0 - 60)';
}
}
$age = 100;
try {
try {
$age = intval($age);
if($age > 60) {
throw new Exception($age);
}
} catch (Exception $e) {
throw new zyfException($age);
}
} catch (zyfException $e) {
echo $e->errorzyfMessage();
}
?>
PHP7中的异常处理
现在写PHP必须考虑版本情况,上面的写法在PHP7中大部分都能实现,但是也会有不同点,在PHP7更新中有一条:更多的Error变为可捕获的Exception,现在的PHP7实现了一个全局的throwable接口,原来老的Exception和其中一部分Error实现了这个接口(interface),PHP7中更多的Error变为可捕获的Exception返回给捕捉器,这样其实和前面提到的扩展try-catch影响范围一样,但是如果不捕获则还是按照Error对待,看下面两个
<?php
try {
test();
} catch(Throwable $e) {
echo $e->getMessage() . ' zyf';
}
try {
test();
} catch(Error $e) {
echo $e->getMessage() . ' zyf';
}
?>
因为PHP7实现了throwable接口,那么就可以使用第一个这种方式来捕获异常。又因为部分Error实现了接口,并且更多的Error变为可捕获的Exception,那么就可以使用第二种方式来捕获异常。下面是在网上找的PHP7的异常层次树:
Throwable
Exception 异常
...
Error 错误
ArithmeticError 算数错误
DivisionByZeroError 除数为0的错误
AssertionError 声明错误
ParseError 解析错误
TypeError 类型错误