如何重写Laravel异常处理类详解


Posted in PHP onDecember 20, 2020

现在开发前后端分离变得越来越流行了,后端只提供接口返回json格式的数据,即使是错误信息也要以json格式来返回,然而目前无论是Laravel框架还是ThinkPHP框架,都只提供了返回json数据的方法,对异常的处理并不是以json格式来返回给我们,所以这里就需要我们自己来改写。

首先我们在app/Exceptions目录新建一个ExceptionHandler.php继承自Handler.php

namespace App\Exceptions;


class ExceptionHandler extends Handler
{

}

然后我们在bootstrap/app.php中,使用我们自定义的异常处理类ExceptionHandler替换掉默认的Handler类

//改为我们自定义的ExceptionHandler类
$app->singleton(
 Illuminate\Contracts\Debug\ExceptionHandler::class,
 App\Exceptions\ExceptionHandler::class
);

接下来我们就开始重写渲染方法

在render方法里,我们根据.env文件中的APP_DEBUG来判断,如果是调试模式,我们还是按照默认方式来渲染错误,如果是非调试模式,我们就返回JSON格式的信息

namespace App\Exceptions;

use Exception;

class ExceptionHandler extends Handler
{
 public function render($request, Exception $exception)
 {
 if (env('APP_DEBUG')) {
  return parent::render($request, $exception);
 }
 return response()->json([
  'code' => $exception->getCode(),
  'msg' => $exception->getMessage()
 ]);
 }
}

这样我们就可以根据APP_DEBUG的值设置是否返回JSON格式的数据了,现在我们把.env的APP_DEBUG的值设为false来测试一下,然后我们故意把代码写错,通过postman或浏览器来访问接口

Route::get('/', function () {
 //这是一段缺少了分号的代码,会报异常
 echo 'Hello World!'
});

如何重写Laravel异常处理类详解

在APP_DEBUG=true的情况下还仍然是默认渲染,方便我们查找错误排错

异常类默认会把异常以日志的形式记录在storage/logs目录下,并且以laravel-日期(YYYY-MM-DD)命名的形式,.log为后缀保存错误日志

如何重写Laravel异常处理类详解

我们打开这个日志文件查看记录的错误信息,我们可以发现错误信息记录的非常详细,除了错误说明之外,还记录了调用栈,如下图所示

如何重写Laravel异常处理类详解

基本上红框里的信息就够我们排错了,不需要像现在这样记录的这么详细,所以要想不记录调用栈,我们可以重写report方法

首先我们看一下框架的report方法,代码在(src/Illuminate/Foundation/Exceptions/Handler.php),我用红框框起来的代码就是调用栈信息,我们在重写这个方法时只需要完全拷贝这个方法里的所有代码到我们自定义的report方法里,然后把红框里的代码去掉即可

如何重写Laravel异常处理类详解

我们在我们自定义的异常处理类ExceptionHandler.php中重写report方法

public function report(Exception $exception)
{
 if ($this->shouldntReport($exception)) {
 return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
 return $this->container->call($reportCallable);
 }

 try {
 $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
 throw $exception;
 }

 $logger->error(
 $exception->getMessage()
 );
}

然后我们再重新请求一下接口再去查看错误日志的记录,可以发现确实没有记录调用栈信息了,但是下面的信息还是不够,我们没法根据下面的信息判断错误发生在哪一个文件和哪一行,如果能在记录错误信息的时候同时记录发生错误的文件和行就更好了,所以借着修改report方法

如何重写Laravel异常处理类详解

public function report(Exception $exception)
{
 if ($this->shouldntReport($exception)) {
 return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
 return $this->container->call($reportCallable);
 }

 try {
 $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
 throw $exception;
 }

 $logger->error(
 $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine()
 );
}

在代码里我通过exception的getFile()、getLine()方法加上了文件和行数,保存代码再次访问接口,查看错误日志文件我们可以看到发生错误的文件和行数已经记录下来了,有了这些信息基本我们就可以找到错误

如何重写Laravel异常处理类详解

截止到这里实现最初的需求我们的ExceptionHandler.php只需要有这些代码

namespace App\Exceptions;


use Exception;
use Illuminate\Support\Reflector;
use Psr\Log\LoggerInterface;

class ExceptionHandler extends Handler
{

 public function render($request, Exception $exception)
 {
 if (env('APP_DEBUG')) {
  return parent::render($request, $exception);
 }
 return response()->json([
  'code' => $exception->getCode(),
  'msg' => $exception->getMessage()
 ]);
 }

 public function report(Exception $exception)
 {
 if ($this->shouldntReport($exception)) {
  return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
  return $this->container->call($reportCallable);
 }

 try {
  $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
  throw $exception;
 }

 $logger->error(
  $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine()
 );
 }
}

然后还不够,我们发现刚刚我们把服务器端的错误信息以JSON格式返回给客户端了,这是不允许的,我们应该只把一些客户端错误返回给客户端,比如密码不足六位、身份证不合法诸如此类,而服务端出现错误时我们只返回给客户端一个模糊的信息即可,比如“服务器错误”,把真实的服务器错误信息记录在日志里面方便开发人员排查错误

所以我们需要定义一个客户端异常专门用户返回客户端错误,使用如下命令在app/Exceptions目录下生成一个ClientException.php文件

php artisan make:exception ClientException

修改为构造方法为如下代码

namespace App\Exceptions;

use Exception;

class ClientException extends Exception
{
 public function __construct($code, $msg)
 {
 parent::__construct($msg, $code);
 }
}

接着我们继续修改ExceptionHandler.php

namespace App\Exceptions;


use Exception;
use Illuminate\Support\Reflector;
use Psr\Log\LoggerInterface;

class ExceptionHandler extends Handler
{
 /**
 * @var int 错误码
 */
 protected $code;
 /**
 * @var string 错误信息
 */
 protected $message;

 protected $dontReport = [
 ClientException::class
 ];

 public function render($request, Exception $exception)
 {
 if ($exception instanceof ClientException) {
  $this->code = $exception->getCode();
  $this->message = $exception->getMessage();
 } else {
  if (env('APP_DEBUG')) {
  return parent::render($request, $exception);
  }
  
  $this->code = 500;
  $this->message = '服务器错误';
 }
 
 return response()->json([
  'code' => $this->code,
  'msg' => $this->message
 ]);
 }

 public function report(Exception $exception)
 {
 if ($this->shouldntReport($exception)) {
  return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
  return $this->container->call($reportCallable);
 }

 try {
  $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
  throw $exception;
 }

 $logger->error(
  $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine()
 );
 }
}

对于上面的修改做一下说明,laravel的$dontReport属性的异常类都不会被上报,因为客户端错误信息我们不需要记录,所以将其添加到$dontReport属性里,并且在render方法里把异常大概分为了两大类,一大类就是客户端异常,另一大类就是服务器异常,我们把服务器异常统一code为500,错误信息为服务器错误,将真实的错误信息记录在了错误日志里,避免把服务器信息暴露给了客户端。

现在我们来测试我们重写异常的结果

假如我们想返回客户端异常,比如没有权限,这类客户端异常在错误日志里都不会产生记录,我们本身也不需要记录

Route::get('/', function () {
 throw new \App\Exceptions\ClientException(403, '你没有权限');
});

如何重写Laravel异常处理类详解

对于服务器端的错误,如少些了分号,客户端就只会知道服务器的某个接口出了问题,但是不清楚具体问题是什么

Route::get('/', function () {
 echo 'Hello World!'
});

如何重写Laravel异常处理类详解

但是真实的错误信息会记录在错误日志里,我们仍旧可以通过错误日志来修改我们服务端的错误

如何重写Laravel异常处理类详解

我们还可以在render方法中加入告警代码,如果是服务端错误就给管理员发送邮件。

至此,我们的重写Laravel异常处理类就算完成啦,希望对正在准备使用Laravel做前后端分离项目的你有所帮助。

到此这篇关于如何重写Laravel异常处理类的文章就介绍到这了,更多相关重写Laravel异常处理类内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP概述.
Oct 09 PHP
删除数组元素实用的PHP数组函数
Aug 18 PHP
PHP对象Object的概念 介绍
Jun 14 PHP
解析PHP获取当前网址及域名的实现代码
Jun 23 PHP
PHP图片处理之图片旋转和图片翻转实例
Nov 19 PHP
php使用NumberFormatter格式化货币的方法
Mar 21 PHP
抛弃 PHP 代价太高
Apr 26 PHP
thinkPHP微信分享接口JSSDK用法实例
Jul 07 PHP
实例讲解YII2中多表关联的使用方法
Jul 21 PHP
Laravel框架集成UEditor编辑器的方法图文与实例详解
Apr 17 PHP
Laravel中正确地返回HTTP状态码方法示例
Sep 10 PHP
PHP生成图表pChart的示例解析
Jul 31 PHP
ThinkPHP6.0如何利用自定义验证规则规范的实现登陆
Dec 16 #PHP
6个常见的 PHP 安全性攻击实例和阻止方法
Dec 16 #PHP
TP5多入口设置实例讲解
Dec 15 #PHP
Mac系统下搭建Nginx+php-fpm实例讲解
Dec 15 #PHP
php在linux环境中如何使用redis详解
Dec 15 #PHP
PHP文件操作简单介绍及函数汇总
Dec 11 #PHP
PHP SESSION跨页面传递失败解决方案
Dec 11 #PHP
You might like
PHP开发框架kohana中处理ajax请求的例子
2014/07/14 PHP
php猴子选大王问题解决方法
2015/05/12 PHP
swoole_process实现进程池的方法示例
2018/10/29 PHP
基于Laravel 5.2 regex验证的正确写法
2019/09/29 PHP
28个JS验证函数收集
2010/03/02 Javascript
基于JavaScript 下namespace 功能的简单分析
2013/07/05 Javascript
利用javascript打开模态对话框(示例代码)
2014/01/11 Javascript
javascript类型转换使用方法
2014/02/08 Javascript
javascript实现客户端兼容各浏览器创建csv并下载的方法
2015/03/23 Javascript
JavaScript与ActionScript3两者的同性与差异性
2016/09/22 Javascript
微信小程序 教程之注册程序
2016/10/17 Javascript
使用BootStrap建立响应式网页——通栏轮播图(carousel)
2016/12/21 Javascript
jQuery.Validate表单验证插件的使用示例详解
2017/01/04 Javascript
jQuery响应滚动条事件功能示例
2017/10/14 jQuery
laravel-admin 与 vue 结合使用实例代码详解
2019/06/04 Javascript
详解微信小程序胶囊按钮返回|首页自定义导航栏功能
2019/06/14 Javascript
[03:55]TI9战队采访——TNC Predator
2019/08/22 DOTA
Python网络爬虫出现乱码问题的解决方法
2017/01/05 Python
Python网络编程详解
2017/10/31 Python
python实现windows壁纸定期更换功能
2019/01/21 Python
Python3使用xml.dom.minidom和xml.etree模块儿解析xml文件封装函数的方法
2019/09/23 Python
django 解决model中类写不到数据库中,数据库无此字段的问题
2020/05/20 Python
keras读取h5文件load_weights、load代码操作
2020/06/12 Python
如何使用Python调整图像大小
2020/09/26 Python
HTML5自定义属性前缀data-及dataset的使用方法(html5 新特性)
2017/08/24 HTML / CSS
Merrell迈乐澳大利亚网站:购买户外登山鞋
2017/05/28 全球购物
瑜伽服装品牌:露露柠檬(lululemon athletica)
2017/06/04 全球购物
加拿大时尚潮流大码女装购物网站:Addition Elle
2018/04/02 全球购物
自荐书模板
2013/12/15 职场文书
园林技术专业求职信
2014/07/28 职场文书
个人授权委托书样本
2014/09/13 职场文书
2014年招商引资工作总结
2014/11/22 职场文书
2014年销售部工作总结
2014/12/01 职场文书
2015年数学教研工作总结
2015/07/22 职场文书
2016年社区综治宣传月活动总结
2016/03/16 职场文书
同学联谊会邀请函
2019/06/24 职场文书