一、PHP异常处理机制

由于我的工作岗位性质,我绝大部分的开发工作涉及到的操作风险都非常高,而且很频繁地使用其他部门提供的接口。所以,对于程序中可能出现的异常和错误都要有相应的处理方法,否则遗漏的话会造成很重大的事故。而且,由于涉及到其他接口的调用,以及高危操作,所以需要对每一步的操作都做日子记录,方便后续出问题后排查定位。

在本篇文章里,我将系统梳理下PHP的错误处理和异常处理方法和机制,让大家意识到在实际项目开发中,对PHP错误和异常处理的重要性。最后介绍Whoops和Monolog组件在实践中的使用。

1.PHP异常是什么

PHP5引入了异常的特性,这是一种新的面向对象的错误处理方法,使用过Java做开发的人应该对异常的概念非常熟悉。

使用try…catch块捕获PHP中出现的异常过程跟其他语言捕获异常处理几乎一样。当抛出异常时,PHP在运行时会去查找能够处理这个异常的catch语句,如果找不到则将异常传递给全局异常处理函数。抛出异常后代码会立即停止执行,后续的PHP代码都不会运行。

异常是从PHP5内置的Exception类(或子类)实例化得到的特殊对象,Exception类对象用于存放和报告错误信息。PHP内置的异常类及其子类如下:

除此之外,PHP标准库还提供了以下额外的Exception子类,用于扩展PHP内置的异常类:

BadFunctionCallException — The BadFunctionCallException class

BadMethodCallException — The BadMethodCallException class

DomainException — The DomainException class

InvalidArgumentException — The InvalidArgumentException class

LengthException — The LengthException class

LogicException — The LogicException class

OutOfBoundsException — The OutOfBoundsException class

OutOfRangeException — The OutOfRangeException class

OverflowException — The OverflowException class

RangeException — The RangeException class

RuntimeException — The RuntimeException class

UnderflowException — The UnderflowException class

UnexpectedValueException — The UnexpectedValueException class

由于PHP中的异常是类,因此我们可以轻易扩展Exception类,使用定制的属性和方法创建自定义的异常类。使用哪个子类由开发者决定,不过选择和创建的异常子类最好能够回答为什么会抛出异常这个问题。

下面是一个简单的异常捕获的例子:

try {

//运行自己的代码

}

catch (exception $e) {

//异常处理代码块

}

2.PHP异常处理关键字

下面这几个关键字时异常处理过程中会经常用到的:

Try:try块里面包含了可能会抛出异常的代码,所有try块里面的代码都会执行知道潜在的异常被抛出来。

Throw:throw关键字表示PHP将要发生异常。PHP运行时会去查找catch语句来处理这个异常。

Catch:这个catch代码块只有Try代码块中发生异常的时候才会被调用,这个时候你就需要在catch代码块中处理好异常。

Finally:在PHP5.5中引入了finally的概念。Finally代码块可以放在catch代码块后面或者取代catch代码块。我们知道try…catch语句中异常处理后就会终止脚本执行,而如果过使用try…catch…finally语句,则无论时正常执行流程还是异常处理流程,finally块中的代码最终都会被执行。所以这个finally代码块中可以放置一些发生异常时关闭资源的情况,如关闭数据库连接等。

try {

//运行自己的代码

}

catch (exception $e) {

//异常处理代码块

}

finally {

//最终处理代码

}

try {

//运行自己的代码

}

finally {

//最终处理代码

}

下图是try…catch…finally的执行顺序:

3.如何捕获和处理异常

对于代码中抛出的异常,我们应该进行相应的处理,否则未捕获的异常会导致PHP应用终止运行,同时显示错误信息,更严重的是,暴露的敏感调试信息可能会给程序带来安全隐患,导致被入侵或破坏。尤其在使用第三方的PHP组件或框架时要做好异常处理。

下面的代码是没有进行异常捕获处理,导致代码奔溃出现异常。

function foo($a, $b) {

if ($b == 0) {

throw new Exception('Division by zero.');

}

return $a / $b;

}

foo(10, 0);

输出:

Fatal error: Uncaught exception ‘Exception’ with message ‘Division by zero.’ in C:\Users\xxxxxxx\error_and_exception\demo4.php:5

Stack trace:

#0 C:\Users\xxxxx\error_and_exception\demo4.php(10): foo(10, 0)

#1 {main}

thrown in C:\Users\xxxxxxx\error_and_exception\demo4.php on line 5

那么,下面我们看看,异常捕获和处理有哪些基本方法呢

(1)Try, throw 和catch

对上面示例中的代码,我们通过try…catch捕获异常。这是程序就不会输出错误的堆栈信息。

function foo($a, $b) {

if ($b == 0) {

throw new Exception('Division by zero.');

}

return $a / $b;

}

try {

foo(10, 0);

} catch (Exception $e) {

echo "Caught exception:" . $e->getMessage();

}

输出:

Caught exception:Division by zero.

(2)自定义的Exception类

由于Exception也是类,我们可以通过继承Exception创建自己的异常子类。

class DivisionException extends Exception {

public function __toString()

{

return "Division by zero.";

}

}

function foo($a, $b) {

if ($b === 0) {

throw new DivisionException('Division by zero.');

}

return $a / $b;

}

try {

foo(10, 0);

} catch (Exception $e) {

echo "Caught exception:" . $e->getMessage();

}

输出:

Caught exception:Division by zero.

(3)多个异常

如何try代码块中可能有多种类型的异常需要处理时,我们可以使用多个catch块进行拦截。同时,我们还可以使用finally语句块,在捕获任何异常以后运行一段代码(未捕获到异常也会运行)。

class DivisionException extends Exception {

public function __toString()

{

return "Division by zero.";

}

}

function foo($a, $b) {

if ($b === 0) {

throw new DivisionException('Division by zero.');

}

return $a / $b;

}

try {

foo(10, 0);

} catch (DivisionException $e) {

echo "Caught division exception: " . $e->getMessage();

} catch (Exception $e) {

echo "Caught other exception:" . $e->getMessage();

} finally {

echo 'Always do this';

}

输出:

Caught division exception: Division by zero.Always do this

第一个catch块会捕获DivisionException异常,第二个catch块会捕获所有类型的异常。捕获某种异常时,指挥运行其中一个catch块,如果PHP没有找到合适的catch块,异常会向上冒泡(调用该代码块所在函数的上一级函数),直到PHP脚本由于Fatal Error错误而终止运行。

(4)全局异常处理函数

如果我们无法确认所有可能抛出的异常,然后又想要尽可能去捕获每一个可能抛出的异常,那这个要怎么实现呢?

我们可以通过注册一个全局异常处理函数,来捕获所有未被捕获的异常。这个全局异常处理函数一定要设置,它是我们最后异常处理的安全保障。对于未捕获的异常,可以通过全局异常处理函数来给PHP应用的用户现实一个合适的错误信息。

全局异常处理函数:

set_exception_handler(function (Exception $e) {

// 处理并记录异常

});

参数可以匿名函数,也可以时回调函数。

function handleException($e)

{

echo "Handle by global exception handler:" . $e->getMessage();

}

set_exception_handler("handleException");

class DivisionException extends Exception {

public function __toString()

{

return "Division by zero.";

}

}

class UnknownException extends Exception {

public function __toString()

{

return "Unknown exception.";

}

}

function foo($a, $b) {

if ($b === 0) {

throw new DivisionException('Division by zero.');

}

if ($a < 0) {

throw new UnknownException('Invalid negative number.');

}

return $a / $b;

}

try {

foo(-10, 2);

} catch (DivisionException $e) {

echo "Caught division exception: " . $e->getMessage();

}

输出:

Handle by global exception handler:Invalid negative number.

上面的代码,我们就通过set_exception_handler函数捕获了一个未被捕获到的异常,应用该函数就可以捕获所有未被捕获的异常。

4.小结

异常处理规则:

可能会抛出异常的代码应该放在try代码块内,以便捕获潜在的异常

每个try或throw代码块必须至少有一个对应的catch代码块

使用多个catch代码块可以捕获不同种类的异常

可以在try代码块内的catch代码块中再次抛出异常

一句话:如果出现了异常,就必须捕获并处理

二、PHP错误处理机制

1.PHP错误分类

PHP错误和异常还是有点差别,PHP脚本由于某些原因而导致无法运行,通常或出发错误。当然,我们也可以通过trigger_error函数自己触发错误,然后使用自定义的错误处理函数进行处理。

现在一共有16个错误级别(参考官网),包括E_ERROR、E_WARNING、E_NOTICE和E_PARSE等。

其错误级别如下:

Parse error >Fatal Error > Waning > Notice

Parse Error语法解析错误:

语法检查阶段报错并中断执行,需要修改代码。

例如未标分号:

$a= 3

输出:

Parse error: syntax error, unexpected end of file in C:\Users\xxxxx\error_and_exception\demo5.php on line 2

Fatal Error错误级别的错误:

程序直接报错并中断执行,需要修改代码,无法通过set_error_handler捕获,可以使用register_shutdown_function在程序终止前出发一个函数。

例如使用未定义的函数:

noexist(); //Fatal error: Call to undefined function noexist()

输出:

Fatal error: Call to undefined function noexist() in C:\Users\xxxx\error_and_exception\demo5.php on line 2

Warning警告级别的错误:

程序出问题,但程序继续执行,需要修改代码。

例如参数个数不一直:

// Warning

$a = ['o' => 2, 4, 6, 8];

// 由于array_sum只能接收一个参数,所以这里会报Warning

$result = array_sum($a, 3);

输出:

Warning: array_sum() expects exactly 1 parameter, 2 given in C:\Users\xxxx\error_and_exception\demo5.php on line 5

Notice通知级别的错误:

如使用一些未定义的变量、或者数组key没有加引号的时候会出现,但程 序继续执行。

例如使用未定义的变量:

// E_NOTICE 运行时通知 表示脚本遇到可能会表现为错误的情况 脚本会继续执行

$a = $b; //Notice: Undefined variable

输出:

Notice: Undefined variable: b in C:\Users\xxx\error_and_exception\demo5.php on line 3

2.PHP错误配置

(1)在脚本中设置

ini_set(‘display_errors’, 0); //关闭错误输出

在脚本中可以通过设置该配置,关闭或开启错误输出。我们的原则是:开发环境开启,生产环境关闭

error_reporting(E_ALL & ~E_NOTICE); //报告 E_NOTICE 之外的所有错误

可以通过设置error_reporting设置想要报告的错误级别。

(2)在php.ini配置文件中配置

上面两个的配置同样可以在php.ini中配置,

error_reporting = E_ALL&~E_NOTICE; //报告E_NOTICE之外的所有错误

display_errors = 1; //输出错误

3.PHP错误处理

(1)set_error_handler

这个错误处理函数只能处理Deprecated、Notice和Warning这三种级别的错误,也就是说无法处理Fatal Error和Parse Error这两种错误。而且这个函数在处理后,脚本会继续执行发生错误的下一行代码。

备注:这里需要注意的是try…catch是无法捕获错误的。

那么,我们能不能对PHP的错误处理也采用异常处理机制来处理呢?答案是可以的,我们可以在设置的错误处理函数抛出异常,这样就可以利用异常处理机制来捕获和处理了。如下:

ini_set('display_errors', 1); //开启错误输出

error_reporting(E_ALL); //报告所有错误级别

function handleError($errno, $errstr, $erfile, $errline) {

throw new Exception($errstr);

}

set_error_handler("handleError");

try {

$a = $b; //Notice: Undefined variable

} catch (Exception $e) {

echo "Caught exceptioin: " . $e->getMessage() . PHP_EOL;

}

try {

// Warning

$a = ['o' => 2, 4, 6, 8];

// 由于array_sum只能接收一个参数,所以这里会报Warning

$result = array_sum($a, 3);

} catch (Exception $e) {

echo "Caught exceptioin: " . $e->getMessage() . PHP_EOL;

}

try {

//set_error_handler无法处理Fatal error错误

noexist(); //Fatal error: Call to undefined function noexist()

} catch (Exception $e) {

echo "Caught exceptioin: " . $e->getMessage() . PHP_EOL;

}

输出:

Caught exceptioin: Undefined variable: b

Caught exceptioin: array_sum() expects exactly 1 parameter, 2 given

Fatal error: Call to undefined function noexist() in C:\Users\xxx\error_and_exception\demo5.php on line 30

同样的Parse error错误set_error_handler也是无法处理的:

try {

//set_error_handler无法处理Parse error错误

$a=3

} catch (Exception $e) {

echo "Caught exceptioin: " . $e->getMessage() . PHP_EOL;

}

输出:

Parse error: syntax error, unexpected ‘}’ in C:\Users\xxxx\error_and_exception\demo5.php on line 38

那么对于Fatal error错误(Parse error在编译时就报错),我们有没有办法用其他方法处理呢?请看下面的函数。

(2)register_shutdown_function

这个方法是在脚本结束前的最后一个回调函数,也就是说无论是错误还是异常还是脚本正常结束都会调用。我们就可以通过注册这个shutdown函数结合error_get_last函数,来对Fatal error错误发生时进行相应的处理,如通知或释放资源之类的操作。

ini_set('display_errors', 1); //开启错误输出

error_reporting(E_ALL); //报告所有错误级别

function shutdown() {

$errstr = error_get_last();

var_dump($errstr);

echo "Last function to execute!";

}

register_shutdown_function('shutdown');

//set_error_handler无法处理Fatal error错误

noexist(); //Fatal error: Call to undefined function noexist()

输出:

Fatal error: Call to undefined function noexist() in C:\Users\guanc\Desktop\error_and_exception\demo6.php on line 15

array(4) {

[“type”]=>

int(1)

[“message”]=>

string(36) “Call to undefined function noexist()”

[“file”]=>

string(52) “C:\Users\guanc\Desktop\error_and_exception\demo6.php”

[“line”]=>

int(15)

}

Last function to execute!

4.小结

最后,总结一下错误处理这一部分的内容。

在开发环境中,我们一般是显示并记录所有错误信息,而在生产环境中我们会让PHP记录大部分错误信息,但是不会显示出来。总的原则是:

一定要让 PHP 报告错误

在开发环境中要显示错误

在生产环境中不能显示错误

在开发环境和生成环境中都要记录错误

三、PHP错误、异常处理实践

1.Whoops组件

在实际开发中,PHP默认的错误显示对阅读时不友好的,排查定位问题也不妨斌。然后通过Whoops组件可以为PHP错误和异常提供精美和易于阅读的诊断页面。

【安装】:通过composer进行安装

composer require filp/whoops

【使用】:Whoops使用很简单,只需要把下列代码放到项目的引导文件中就可以。如果想要捕获所有的错误和异常,就必须在程序的开头注册组件。

$whoops = new \Whoops\Run;

$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);

$whoops->register();

示例:

require __DIR__ . '/vendor/autoload.php';

$whoops = new \Whoops\Run;

$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);

$whoops->register();

$a = $b;

然后通过浏览器访问这个php脚本,可以看到下面这个很漂亮的诊断页面:

2.Monolog组件

monolog是日志记录组件,遵循PSR-3规范。我们在处理PHP错误和异常时,无论是生产环境还是开发环境,都需要把错误和异常记录下来。Monolog作为日志组件能很出色完成这个任务。

【安装】:通过composer进行安装

composer require monolog/monolog

【使用】:

require __DIR__ . '/vendor/autoload.php';

use Monolog\Logger;

use Monolog\Handler\StreamHandler;

try {

// create a log channel

$log = new Logger('name');

$logName = date('Y-m-d',time()) . '.log';

$log->pushHandler(new StreamHandler($logName, Logger::DEBUG));

// add records to the log

$log->info('This is a test');

$log->warning('Foo');

$log->error('Bar');

} catch (Exception $e) {

var_dump($e->getMessage());

}

我们看下记录的日志:

λ cat 2018-12-16.log

[2018-12-16 12:26:52] name.INFO: This is a test [] []

[2018-12-16 12:26:52] name.WARNING: Foo [] []

[2018-12-16 12:26:52] name.ERROR: Bar [] []

打赏

微信扫一扫,打赏作者吧~

PHP加密时遇到try错误,深入学习PHP错误与异常处理相关推荐

  1. go http 处理w.write 错误_go学习笔记-错误处理

    go语言对异常的处理没有使用其他编程语言中常见的 try---catch来处理.go语言追求简洁优雅.在go语言,没有传统的异常概念,go使用panic和recover机制对程序的严重异常进行处理(例 ...

  2. Spring学习中使用javaConfig进行配置时出现 has not been refreshed yet错误

    Spring学习中使用javaConfig进行配置时出现 has not been refreshed yet错误 java.lang.IllegalStateException: org.sprin ...

  3. Qt 学习:comboBox编程时使用currentIndexChanged老是出现 assert failure错误

    Qt5 学习新手,所以用comboBox的 indexChanged槽时不知为何总是出现 ASSERT failure 错误: 在程序中由于还使用了QList的对象,起初以为是它出了问题,反复检查也没 ...

  4. python凯撒密码加密写入文件_Python用户名密码登录系统(MD5加密并存入文件,三次输入错误将被锁定)及对字符串进行凯撒密码加解密操作...

    #-*- coding: gb2312 -*-#用户名密码登录系统(MD5加密并存入文件)及对字符串进行凯撒密码加解密操作#作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/k ...

  5. xjar加密后运行错误_Python中的错误和异常

    前言 错误是程序中的问题,由于这些问题而导致程序停止执行.另一方面,当某些内部事件发生时,会引发异常,从而改变程序的正常流程. python中会发生两种类型的错误. 语法错误 逻辑错误(异常) 语法错 ...

  6. 深度学习入门 错误汇总

    深度学习入门 错误汇总 新程序一打开一堆错误 解决:配置环境 不能正常训练,且报错 OMP: Error #15: Initializing libiomp5md.dll, but found lib ...

  7. 软件加密时保护软件著作权要注意避免的思路误区

    软件加密时保护软件著作权要注意避免的思路误区 一.问题的提出 首先引用有关"ECC加密算法"介绍的原文结尾部分内容: "七.椭圆曲线在软件注册保护的应用 ....... ...

  8. error 系统错误 错误码10007_Python学习之错误调试和测试

    Python学习之错误调试和测试 Python学习目录 在Mac下使用Python3 Python学习之数据类型 Python学习之函数 Python学习之高级特性 Python学习之函数式编程 Py ...

  9. 【Go语言刷题篇】Go完结篇|函数、结构体、接口、错误入门学习

    Go从0到入门6-Go完结篇 前言 Q1:函数-数字的阶乘 Q2:函数-绝对值 Q3:函数-加减乘除 Q4:结构体-学生信息1 Q5:结构体-学生信息2 Q6:接口-动物和老虎 Q7:错误-网络延迟 ...

最新文章

  1. 在 Java Web 项目中,Service 层和 Dao 层真的有必要每个类都加上接口吗
  2. django引入现有数据库
  3. SQL Server通配符妙用
  4. docker 部署nginx
  5. 今天的新坑 ubuntu18.04安装docker
  6. 基于js对象,操作属性、方法详解
  7. ubuntu 上网总结
  8. [eclipse]Syntax error on token ;,{ expected after this token
  9. APP自动化测试系列之获取Android的Activity 和 Package
  10. IT部门不应忽略的12种数据
  11. 用于创建此对象的程序是excel_一起学Excel专业开发22:使用类模块创建对象1
  12. plsql11破解注册码
  13. 服务器主板支持nvme,给老主板刷上一个加入支持NVMe模块的改版“BIOS”
  14. Http状态码406(Not Acceptable)
  15. (翻译)按钮的对比色引导用户操作的方式
  16. Activiti表结构
  17. iOS高德地图去logo
  18. 【2】C++语法与数据结构之MFC_CList学生管理系统_链表内排序_函数指针
  19. JavaScript 日期操作我不知道的事情
  20. 存在为退还的延长失保金支付记录,需退还后才能就业登记

热门文章

  1. 餐饮供应链管理系统解决方案
  2. Go框架比较:goframe、beego、iris和gin
  3. (二)MkDocs学习笔记——撰写文档
  4. 视觉SLAM_07_相机模型
  5. 应用系统负载分析与磁盘容量预测
  6. 触摸按键程序key.c
  7. cogs729. [网络流24题] 圆桌聚餐
  8. Oracle的简易理解
  9. CodeForces ~ 996B ~ World Cup (思维)
  10. Scapy Sniffer的用法