PHP将抛出一个错误问题,怎么解决
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP将抛出一个错误问题,怎么解决相关的知识,希望对你有一定的参考价值。
参考技术A 首先要知道什么是php异常?异常(Exception)用于在指定的错误发生时改变脚本的正常流程。
PHP 5 提供了一种新的面向对象的错误处理方法。
异常处理用于在指定的错误(异常)情况发生时改变脚本的正常流程。这种情况称为异常。
当异常被触发时,通常会发生:
当前代码状态被保存
代码执行被切换到预定义的异常处理器函数
根据情况,处理器也许会从保存的代码状态重新开始执行代码,终止脚本执行,或从代码中另外的位置继续执行脚本
我们将展示不同的错误处理方法:
异常的基本使用
创建自定义的异常处理器
多个异常
重新抛出异常
设置顶层异常处理器
异常的基本使用
当异常被抛出时,其后的代码不会继续执行,PHP 会尝试查找匹配的 “catch” 代码块。
如果异常没有被捕获,而且又没用使用 set_exception_handler() 作相应的处理的话,那么将发生一个严重的错误(致命错误),并且输出 “Uncaught Exception” (未捕获异常)的错误消息。
让我们尝试抛出一个异常,同时不去捕获它:
复制代码 代码如下:
<?php
//create function with an exceptionfunction
checkNum($number)
if($number>1)
throw new Exception(”Value must be 1 or below”);
return true;
//trigger
exceptioncheckNum(2);
?>
上面的代码会获得类似这样的一个错误:
Fatal error: Uncaught exception ‘Exception' with message ‘Value must be 1 or below' in C:\webfolder\test.php:6 Stack trace: #0 C:\webfolder\test.php(12): checkNum(28) #1 main thrown in C:\webfolder\test.php on line 6
Try, throw 和 catch
要避免上面例子出现的错误,我们需要创建适当的代码来处理异常。
处理处理程序应当包括:
Try - 使用异常的函数应该位于 “try” 代码块内。如果没有触发异常,则代码将照常继续执行。但是如果异常被触发,会抛出一个异常。
Throw - 这里规定如何触发异常。每一个 “throw” 必须对应至少一个 “catch”
Catch - “catch” 代码块会捕获异常,并创建一个包含异常信息的对象
让我们触发一个异常:
<?php//创建可抛出一个异常的函数function checkNum($number) if($number>1) throw new Exception(”Value must be 1 or below”); return true; //在 “try” 代码块中触发异常try checkNum(2); //If the exception is thrown, this text will not be shown echo ‘If you see this, the number is 1 or below'; //捕获异常catch(Exception $e) echo ‘Message: ‘ .$e->getMessage(); ?>
上面代码将获得类似这样一个错误:
Message: Value must be 1 or below
例子解释:
上面的代码抛出了一个异常,并捕获了它:
创建 checkNum() 函数。它检测数字是否大于 1。如果是,则抛出一个异常。
在 “try” 代码块中调用 checkNum() 函数。
checkNum() 函数中的异常被抛出
“catch” 代码块接收到该异常,并创建一个包含异常信息的对象 ($e)。
通过从这个 exception 对象调用 $e->getMessage(),输出来自该异常的错误消息
不过,为了遵循“每个 throw 必须对应一个 catch”的原则,可以设置一个顶层的异常处理器来处理漏掉的错误。
创建一个自定义的 Exception 类
创建自定义的异常处理程序非常简单。我们简单地创建了一个专门的类,当 PHP 中发生异常时,可调用其函数。该类必须是 exception 类的一个扩展。
这个自定义的 exception 类继承了 PHP 的 exception 类的所有属性,您可向其添加自定义的函数。
我们开始创建 exception 类:
复制代码 代码如下:
<?php
class customException extends Exception
public function errorMessage()
//error message
$errorMsg = ‘Error on line ‘.$this->getLine().' in ‘.$this->getFile() .': <b>'.$this->getMessage().'</b> is not a valid E-Mail address'; return $errorMsg;
$email = “someone@example…com”;try
//check if
if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
//throw exception if email is not valid throw
new customException($email);
catch (customException $e)
//display custom message
echo $e->errorMessage();
?>
这个新的类是旧的 exception 类的副本,外加 errorMessage() 函数。正因为它是旧类的副本,因此它从旧类继承了属性和方法,我们可以使用 exception 类的方法,比如 getLine() 、 getFile() 以及 getMessage()。
例子解释:
上面的代码抛出了一个异常,并通过一个自定义的 exception 类来捕获它:
customException() 类是作为旧的 exception 类的一个扩展来创建的。这样它就继承了旧类的所有属性和方法。
创建 errorMessage() 函数。如果 e-mail 地址不合法,则该函数返回一条错误消息
把 $email 变量设置为不合法的 e-mail 地址字符串
执行 “try” 代码块,由于 e-mail 地址不合法,因此抛出一个异常
“catch” 代码块捕获异常,并显示错误消息
多个异常
可以为一段脚本使用多个异常,来检测多种情况。
可以使用多个 if..else 代码块,或一个 switch 代码块,或者嵌套多个异常。这些异常能够使用不同的 exception 类,并返回不同的错误消息:
复制代码 代码如下:
<?php
class customException extends Exceptionpublic function errorMessage()
//error
message$errorMsg = ‘Error on line ‘.$this->getLine().' in ‘.$this->getFile().': <b>'.$this->getMessage().'</b> is not a valid E-Mail address';
return $errorMsg;
$email = “someone@example.com”;try
//check if
if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
//throw exception if email is not valid throw new
customException($email);
//check for “example” in mail address
if(strpos($email, “example”) !== FALSE) throw new Exception(”$email is an example e-mail”); catch (customException $e) echo $e->errorMessage(); catch(Exception $e) echo $e->getMessage(); ?>
例子解释:
上面的代码测试了两种条件,如何任何条件不成立,则抛出一个异常:
customException() 类是作为旧的 exception 类的一个扩展来创建的。这样它就继承了旧类的所有属性和方法。
创建 errorMessage() 函数。如果 e-mail 地址不合法,则该函数返回一个错误消息。
执行 “try” 代码块,在第一个条件下,不会抛出异常。
由于 e-mail 含有字符串 “example”,第二个条件会触发异常。
“catch” 代码块会捕获异常,并显示恰当的错误消息
如果没有捕获 customException,紧紧捕获了 base exception,则在那里处理异常。
重新抛出异常
有时,当异常被抛出时,您也许希望以不同于标准的方式对它进行处理。可以在一个 “catch” 代码块中再次抛出异常。
脚本应该对用户隐藏系统错误。对程序员来说,系统错误也许很重要,但是用户对它们并不感兴趣。为了让用户更容易使用,您可以再次抛出带有对用户比较友好的消息的异常:
复制代码 代码如下:
<?php
class customException extends Exception public function errorMessage()
//error message
$errorMsg = $this->getMessage().' is not a valid E-Mail address.'; return $errorMsg; $email = “someone@example.com”;try try
//check for “example” in mail address
if(strpos($email, “example”) !== FALSE)
//throw exception if email is not valid throw new
Exception($email); catch(Exception $e)
//re-throw exception throw new
customException($email); catch (customException $e)
//display custom message
echo $e->errorMessage();
?>
例子解释:
上面的代码检测在邮件地址中是否含有字符串 “example”。如果有,则再次抛出异常:
customException() 类是作为旧的 exception 类的一个扩展来创建的。这样它就继承了旧类的所有属性和方法。
创建 errorMessage() 函数。如果 e-mail 地址不合法,则该函数返回一个错误消息。
把 $email 变量设置为一个有效的邮件地址,但含有字符串 “example”。
“try” 代码块包含另一个 “try” 代码块,这样就可以再次抛出异常。
由于 e-mail 包含字符串 “example”,因此触发异常。
“catch” 捕获到该异常,并重新抛出 “customException”。
捕获到 “customException”,并显示一条错误消息。
如果在其目前的 “try” 代码块中异常没有被捕获,则它将在更高层级上查找 catch 代码块。
设置顶层异常处理器 (Top Level Exception Handler)
set_exception_handler() 函数可设置处理所有未捕获异常的用户定义函数。
复制代码 代码如下:
<?php
function myException($exception)
echo “<b>Exception:</b> ” , $exception->getMessage();
set_exception_handler('myException');
throw new Exception('Uncaught Exception occurred');
?>
以上代码的输出应该类似这样:
Exception: Uncaught Exception occurred
在上面的代码中,不存在 “catch” 代码块,而是触发顶层的异常处理程序。应该使用此函数来捕获所有未被捕获的异常。
异常的规则
需要进行异常处理的代码应该放入 try 代码块内,以便捕获潜在的异常。
每个 try 或 throw 代码块必须至少拥有一个对应的 catch 代码块。
使用多个 catch 代码块可以捕获不同种类的异常。
可以在 try 代码块内的 catch 代码块中再次抛出(re-thrown)异常。
简而言之:如果抛出了异常,就必须捕获它。
不要将抛出异常作为业务逻辑使用
一个奇怪的用法
我遇到过一个项目,使用 .net core 搭建的一套OA系统,搭建这个系统的架构师,选择将不合法的请求,或是失败的请求用 throw 抛出异常,然后再异常过滤器中打包成一个正常相应的数据,返回给前端。
例:服务器接受到客户端的请求,检查上传的参数的时候,发现少了一个参数,这个时候应该告诉客户端:你少给我了一个参数。在这个系统中,要求所有的工程师遇到这个种情况要抛出异常,并在异常信息里写明要返回给客户端的描述信息。就是:
if (参数少了一个) { throw new Exception("前端你的参数少了一个"); }
然后在异常过滤器中将异常信息包装成一个正常返回值,主动抛出的异常描述信息也在这个返回值中返回给前端。
现在请思考下这种设计有没有什么不妥?
这种方式完全违背了异常机制的初衷,将异常机制大材小用,而唯一的好处就是写起来短。
异常机制表示这不是一个正常的业务代码,怎么理解?异常的抛出会停止系统的所有接下来的操作,这是强制性的。为什么这么做?为什么要停下来?是因为抛出异常代表发生了一个不可预知的危险操作,代表发生了正常业务逻辑以外的事,如果继续执行下去,可能会发生无法挽回的危险。所以需要一个抛出的操作强制终止程序的执行。
写个除法器
举个简单的例子,你需要写一个方法来计算两个数相除的商,你可以很快地写下这个方法:
double Div(double n1, double n2) { return n1 / n2; }
那如果除数 n2 接收到的是 0 ,怎么办?
在 .net 里除数为 0 的话会直接抛出异常,但如果让你来实现除法的话,你要这么应对这种情况呢?
可能有人会想到,多返回两个结果参数,一个用来指示计算是否成功,一个指示失败的原因,一共有三个参数,就像:
(bool, string, double) Div(double n1, double n2) { // }
这个可能是一个比较周全的方法,考虑到成功和失败的情况,但请思考一下,如果除数传进来是 0 ,这个方法会返回 (false, "除数不能为零", ??)。商的返回值应该是什么?有人会想:商的返回值是什么都可以,因为已经有 false 指示相除失败,也有说明失败原因的结果参数了。调用方只要先判断相除是否成功的参数就可以了。
没错,理想情况的确是这样,那么,你要怎么保证调用方一定会按照你的想法去使用这个方法呢?这个时候可能有人会想:那我管不着!别人要乱用我有什么办法!
对,这么想当然没问题,但我们是软件工程师,我们可以做的更好。你想一下,如果你的这个方法被使用在金融系统中,可能会造成多大的损失。无论你的返回值多么详尽,这仍然是一个不安全的方法。在调用方传递了一个不合法的值 0 后,仍然在商的结果位给了一个数字,而你是没有办法保证调用方不会那你的返回的错误结果商去当作正常的值使用,这可能会在金融系统中造成无法估量的损失,比如算少了你的工资。
这个时候我们应该怎么做,才能帮用户最大程度上减少损失?
抛出异常的妙用
我们要么确保一定给调用方返回一个正确的值,要么在计算不出正确的值时,什么都不返回。这就是需要抛出异常机制的时候了。
在接受到 0 作为除数时,直接抛出错误,阻止程序继续往下走。这样,在调用方给你一个错误的参数,你就阻止了调用方可能会做的胡搞瞎搞,从而迫使调用方检查他的参数。
尽管异常会造成一些性能损耗,但和可能发生的业务损失相比,这些性能损耗太微不足道了。
明白了吗,抛出异常的使用,不是作为正常的业务流程所使用,而是当发生你无法预计的不正常业务流程时,阻止他继续可能会造成的损失来使用的。像开头我提到了架构师搭建的项目,将抛出作为正常业务流程的一部分来使用,这使得抛出这个动作发生得十分频繁。因为你检查客户端的参数,这时一个正常的流程,你能知道那些参数时正确的,那些是不正确的,并知道要怎么提醒客户端。如果这里再检查参数不正确后就主动抛出,因为可能发生得十分,就会十分损耗新能。这是十分不可取的一种方式。
思考到这里,想一下,如果你调用的别人的代码抛出了异常,你要怎么办呢?是 try catch 捕获之后,当无事发生吗?这就浪费了别人抛出的一番苦心了,你要做的是,检查修改你的代码,看看参数是不是不正确,完善逻辑,以保证不会再诱发下一次同样的报错。如果你不能保证,并且也不能承担 try catch 后可能会发生的损失,那就让它报错。
以上是关于PHP将抛出一个错误问题,怎么解决的主要内容,如果未能解决你的问题,请参考以下文章