异常处理是编程中十分重要但也最容易被人忽视的语言特性,它为开发者提供了处理程序运行时错误的机制,对于程序设计来说正确的异常处理能够防止泄露程序自身细节给用户,给开发者提供完整的错误回溯堆栈,同时也能提高程序的健壮性。

这篇文章我们来简单梳理一下Laravel中提供的异常处理能力,然后讲一些在开发中使用异常处理的实践,如何使用自定义异常、如何扩展Laravel的异常处理能力。

注册异常Handler

这里又要回到我们说过很多次的Kernel处理请求前的bootstrap阶段,在bootstrap阶段的IlluminateFoundationBootstrapHandleExceptions 部分中Laravel设置了系统异常处理行为并注册了全局的异常处理器:

class HandleExceptions
{public function bootstrap(Application $app){$this->app = $app;error_reporting(-1);set_error_handler([$this, 'handleError']);set_exception_handler([$this, 'handleException']);register_shutdown_function([$this, 'handleShutdown']);if (! $app->environment('testing')) {ini_set('display_errors', 'Off');}}public function handleError($level, $message, $file = '', $line = 0, $context = []){if (error_reporting() & $level) {throw new ErrorException($message, 0, $level, $file, $line);}}
}

set_exception_handler([$this, 'handleException'])HandleExceptionshandleException方法注册为程序的全局处理器方法:

public function handleException($e)
{if (! $e instanceof Exception) {$e = new FatalThrowableError($e);}$this->getExceptionHandler()->report($e);if ($this->app->runningInConsole()) {$this->renderForConsole($e);} else {$this->renderHttpResponse($e);}
}protected function getExceptionHandler()
{return $this->app->make(ExceptionHandler::class);
}// 渲染CLI请求的异常响应
protected function renderForConsole(Exception $e)
{$this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
}// 渲染HTTP请求的异常响应
protected function renderHttpResponse(Exception $e)
{$this->getExceptionHandler()->render($this->app['request'], $e)->send();
}

在处理器里主要通过ExceptionHandlerreport方法上报异常、这里是记录异常到storage/laravel.log文件中,然后根据请求类型渲染异常的响应生成输出给到客户端。这里的ExceptionHandler就是AppExceptionsHandler类的实例,它是在项目最开始注册到服务容器中的:

// bootstrap/app.php/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
*/$app = new IlluminateFoundationApplication(realpath(__DIR__.'/../')
);/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
*/
......$app->singleton(IlluminateContractsDebugExceptionHandler::class,AppExceptionsHandler::class
);

这里再顺便说一下set_error_handler函数,它的作用是注册错误处理器函数,因为在一些年代久远的代码或者类库中大多是采用PHP那件函数trigger_error函数来抛出错误的,异常处理器只能处理Exception不能处理Error,所以为了能够兼容老类库通常都会使用set_error_handler注册全局的错误处理器方法,在方法中捕获到错误后将错误转化成异常再重新抛出,这样项目中所有的代码没有被正确执行时都能抛出异常实例了。

/*** Convert PHP errors to ErrorException instances.** @param  int  $level* @param  string  $message* @param  string  $file* @param  int  $line* @param  array  $context* @return void** @throws ErrorException*/
public function handleError($level, $message, $file = '', $line = 0, $context = [])
{if (error_reporting() & $level) {throw new ErrorException($message, 0, $level, $file, $line);}
}

常用的Laravel异常实例

Laravel中针对常见的程序异常情况抛出了相应的异常实例,这让开发者能够捕获这些运行时异常并根据自己的需要来做后续处理(比如:在catch中调用另外一个补救方法、记录异常到日志文件、发送报警邮件、短信)

在这里我列一些开发中常遇到异常,并说明他们是在什么情况下被抛出的,平时编码中一定要注意在程序里捕获这些异常做好异常处理才能让程序更健壮。

  • IlluminateDatabaseQueryException Laravel中执行SQL语句发生错误时会抛出此异常,它也是使用率最高的异常,用来捕获SQL执行错误,比方执行Update语句时很多人喜欢判断SQL执行后判断被修改的行数来判断UPDATE是否成功,但有的情景里执行的UPDATE语句并没有修改记录值,这种情况就没法通过被修改函数来判断UPDATE是否成功了,另外在事务执行中如果捕获到QueryException 可以在catch代码块中回滚事务。
  • IlluminateDatabaseEloquentModelNotFoundException 通过模型的findOrFailfirstOrFail方法获取单条记录时如果没有找到会抛出这个异常(findfirst找不到数据时会返回NULL)。
  • IlluminateValidationValidationException 请求未通过Laravel的FormValidator验证时会抛出此异常。
  • IlluminateAuthAccessAuthorizationException 用户请求未通过Laravel的策略(Policy)验证时抛出此异常
  • SymfonyComponentRoutingExceptionMethodNotAllowedException 请求路由时HTTP Method不正确
  • IlluminateHttpExceptionsHttpResponseException Laravel的处理HTTP请求不成功时抛出此异常

扩展Laravel的异常处理器

上面说了Laravel把AppExceptionsHandler 注册成功了全局的异常处理器,代码中没有被catch到的异常,最后都会被AppExceptionsHandler捕获到,处理器先上报异常记录到日志文件里然后渲染异常响应再发送响应给客户端。但是自带的异常处理器的方法并不好用,很多时候我们想把异常上报到邮件或者是错误日志系统中,下面的例子是将异常上报到Sentry系统中,Sentry是一个错误收集服务非常好用:

public function report(Exception $exception)
{if (app()->bound('sentry') && $this->shouldReport($exception)) {app('sentry')->captureException($exception);}parent::report($exception);
}

还有默认的渲染方法在表单验证时生成响应的JSON格式往往跟我们项目里统一的JOSN格式不一样这就需要我们自定义渲染方法的行为。

public function render($request, Exception $exception)
{//如果客户端预期的是JSON响应,  在API请求未通过Validator验证抛出ValidationException后//这里来定制返回给客户端的响应.if ($exception instanceof ValidationException && $request->expectsJson()) {return $this->error(422, $exception->errors());}if ($exception instanceof ModelNotFoundException && $request->expectsJson()) {//捕获路由模型绑定在数据库中找不到模型后抛出的NotFoundHttpExceptionreturn $this->error(424, 'resource not found.');}if ($exception instanceof AuthorizationException) {//捕获不符合权限时抛出的 AuthorizationExceptionreturn $this->error(403, "Permission does not exist.");}return parent::render($request, $exception);
}

自定义后,在请求未通过FormValidator验证时会抛出ValidationException, 之后异常处理器捕获到异常后会把错误提示格式化为项目统一的JSON响应格式并输出给客户端。这样在我们的控制器中就完全省略了判断表单验证是否通过如果不通过再输出错误响应给客户端的逻辑了,将这部分逻辑交给了统一的异常处理器来执行能让控制器方法瘦身不少。

使用自定义异常

这部分内容其实不是针对Laravel框架自定义异常,在任何项目中都可以应用我这里说的自定义异常。

我见过很多人在Repository或者Service类的方法中会根据不同错误返回不同的数组,里面包含着响应的错误码和错误信息,这么做当然是可以满足开发需求的,但是并不能记录发生异常时的应用的运行时上下文,发生错误时没办法记录到上下文信息就非常不利于开发者进行问题定位。

下面的是一个自定义的异常类

namespace AppExceptions;use RuntimeException;
use Throwable;class UserManageException extends RuntimeException
{/*** The primitive arguments that triggered this exception** @var array*/public $primitives;/*** QueueManageException constructor.* @param array $primitives* @param string $message* @param int $code* @param Throwable|null $previous*/public function __construct(array $primitives, $message = "", $code = 0, Throwable $previous = null){parent::__construct($message, $code, $previous);$this->primitives = $primitives;}/*** get the primitive arguments that triggered this exception*/public function getPrimitives(){return $this->primitives;}
}

定义完异常类我们就能在代码逻辑中抛出异常实例了

class UserRepository
{public function updateUserFavorites(User $user, $favoriteData){......if (!$executionOne) {throw new UserManageException(func_get_args(), 'Update user favorites error', '501');}......if (!$executionTwo) {throw new UserManageException(func_get_args(), 'Another Error', '502');}return true;}
}class UserController extends ...
{public function updateFavorites(User $user, Request $request){.......$favoriteData = $request->input('favorites');try {$this->userRepo->updateUserFavorites($user, $favoritesData);} catch (UserManageException $ex) {.......}}
}

除了上面Repository列出的情况更多的时候我们是在捕获到上面列举的通用异常后在catch代码块中抛出与业务相关的更细化的异常实例方便开发者定位问题,我们将上面的updateUserFavorites 按照这种策略修改一下

public function updateUserFavorites(User $user, $favoriteData)
{try {// database execution// database execution} catch (QueryException $queryException) {throw new UserManageException(func_get_args(), 'Error Message', '501' , $queryException);}return true;
}

在上面定义UserMangeException类的时候第四个参数$previous是一个实现了Throwable接口类实例,在这种情景下我们因为捕获到了QueryException的异常实例而抛出了UserManagerException的实例,然后通过这个参数将QueryException实例传递给PHP异常的堆栈,这提供给我们回溯整个异常的能力来获取更多上下文信息,而不是仅仅只是当前抛出的异常实例的上下文信息, 在错误收集系统可以使用类似下面的代码来获取所有异常的信息。

while($e instanceof Exception) {echo $e->getMessage();$e = $e->getPrevious();
}

异常处理是PHP非常重要但又容易让开发者忽略的功能,这篇文章简单解释了Laravel内部异常处理的机制以及扩展Laravel异常处理的方式方法。更多的篇幅着重分享了一些异常处理的编程实践,这些正是我希望每个读者都能看明白并实践下去的一些编程习惯,包括之前分享的Interface的应用也是一样。

本文已经收录在系列文章

kevinyan815/Learning_Laravel_Kernel​github.com

欢迎访问阅读

也欢迎关注我的公众号 网管叨bi叨 ,最近正在筹备准备分享一些日常工作里学到和总结的技术知识,也会分享一些见闻和学习英语的方法。

C++ Qt全局异常处理器_异常处理相关推荐

  1. C++ Qt全局异常处理器_QT教程

    点击上方 蓝字关注我们 今日资源 今天的资源是另一套QT教程,某马发的,网上搜集到的.这已经是这个公众号发放的第三套QT教程了. 关于QT:Qt(官方发音 [kju:t],音同 cute)是一个跨平台 ...

  2. SpringMVC使用HandlerExceptionResolver实现全局异常处理器

    在SpringMVC中,提供了一个全局异常处理器,用于对系统中出现的异常进行统一处理.在一般的系统中,DAO层.Service层及Controller层出现异常都以"throws Excep ...

  3. SpringMVC构建全局异常处理器

    异常处理器 SpringMVC提供全局异常处理器进行统一的异常处理,一个系统只能有一个异常处理器. 系统的dao.service.controller层出现异常的时候都throws Exception ...

  4. 全局异常处理器——优雅的处理异常

    是什么 全局异常处理器即 把错误异常统一处理的方法.Java开发过程中,不可避免的会遇到各种异常情况,如果不做处理往往会导致程序崩溃!面对这种情况,传统方式就是使用try/catch去捕获并处理异常, ...

  5. springboot全局异常处理器

    通过aop监控程序异常,全局异常捕获 定义全局异常处理器: import java.util.HashMap; import java.util.Map; import org.springframe ...

  6. SpringBoot中定义全局异常处理器

    场景 SpringBoot中创建全局异常处理器,使发生异常时能处理. 项目搭建专栏: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/column/info/356 ...

  7. spring设置全局异常处理器

    spring设置全局异常处理器 参考文章: (1)spring设置全局异常处理器 (2)https://www.cnblogs.com/gongxijun/p/5824723.html 备忘一下.

  8. SpringBoot 自定义全局异常处理器

    SpringBoot自定义全局异常处理器 一.maven依赖 二.GlobalExceptionHandler.java 三.ResponseStandard.java 四.logback.xml 五 ...

  9. springBoot配置全局异常处理器

    一.自定义自己的异常类MyException.java public class MyException extends RuntimeException {private int code;publ ...

最新文章

  1. 不丢失精度的获取照片的Gps经纬度
  2. JavaScript中正则表达式学习(一)
  3. 你想学Java?资源都在这里了
  4. Pinterest从OpenTSDB切换到他们自己的时间序列数据库
  5. eSpeak: Linux 文本转语音工具
  6. C++对象模型详解(上)
  7. Python自动解压各种压缩文件
  8. mfc工程中添加com接口支持
  9. centos5.6安装mysql_centos安装mysql5.6的正确姿态
  10. 腾讯X5联手白鹭Egret Runtime 合作共推HTML5游戏发展
  11. javafx android sdk,JavaFX打包到Android上
  12. Qt中标绘功能的实现方法对比
  13. android digest 认证,探究 Android 签名机制和原理
  14. 强者不能恒强,偏向虎山行
  15. SPSS编程-统计自动化-VBA脚本开发
  16. python解析库 爬虫_Python-爬虫-解析库(Beautiful Soup)的使用
  17. WCDMA常见缩略语
  18. ArcGIS Engine二次开发常用接口及其使用方法(一)
  19. android 设置默认语言
  20. curl put请求

热门文章

  1. UI设计师收好!哪些会说故事的插画素材!
  2. python运算符讲解_举例讲解Python中的算数运算符的用法
  3. CEF编译教程(手把手教学版)
  4. Linux内核:kprobe机制-探测点
  5. srsLTE源码学习:RLC,无线链路控制子层抓包rlc_pcap.h,rlc_pcap.cc
  6. __attribute__((visibility()))
  7. BSD:Berkeley Software Distribution,伯克利软件套件
  8. 【linux指令】dialog实现终端下的GUI-2
  9. ncurses输出修饰:attron(),attroff(),attrset(),attr_get(),attr_,wattr_,chgat(),mvchgat()
  10. BP神经网络原理简介