照猫画虎 实现 min-laravel 框架系列之异常

php   laravel  

异常

异常捕获机制让我们可以更友好向客户端展示错误信息,laravel 在发生错误|异常时,通常会分两步走:

1、report:上报错误,主要是进行记录日志,发送报错邮件等
2、render:组织渲染携带报错|异常信息的影响给客户端

在生成异常响应时,仍然是建立在 symfony/http-foundation 库的基础上的,因此需要对这个包有一定的了解

异常处理整体祖册处理流程

  • 服务提供者加载到系统

    1. // Illuminate\Foundation\Http\Kernel::$bootstrappers 配置中
    2. protected $bootstrappers = [
    3. ...
    4. \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    5. ...
    6. ];
  • 各种异常处理函数注册 Illuminate\Foundation\Bootstrap\HandleExceptions::bootstrap()

  • 异常处理类绑定

    1. // app/bootstrap/app.php
    2. $app->singleton(
    3. Illuminate\Contracts\Debug\ExceptionHandler::class,
    4. App\Exceptions\Handler::class
    5. );
  • 异常抛出时,由 App\Exceptions\Handler 类进行处理

异常服务提供者

Illuminate\Foundation\Bootstrap\HandleExceptions 类

注册处理函数

  1. public function bootstrap(Application $app)
  2. {
  3. // 预先占用系统一些内存
  4. self::$reservedMemory = str_repeat('x', 10240);
  5. $this->app = $app;
  6. error_reporting(-1);
  7. // 错误处理函数
  8. set_error_handler([$this, 'handleError']);
  9. // 异常处理函数
  10. set_exception_handler([$this, 'handleException']);
  11. // 程序意外关闭时的处理函数
  12. register_shutdown_function([$this, 'handleShutdown']);
  13. if (! $app->environment('testing')) {
  14. ini_set('display_errors', 'Off');
  15. }
  16. }

异常处理函数 handleException

  1. public function handleException(Throwable $e)
  2. {
  3. try {
  4. // 预先流出的一块内存
  5. self::$reservedMemory = null;
  6. // 获取处理类,上报异常信息
  7. $this->getExceptionHandler()->report($e);
  8. } catch (Exception $e) {
  9. //
  10. }
  11. // 分 web|console 两种情况生成请求响应
  12. if ($this->app->runningInConsole()) {
  13. $this->renderForConsole($e);
  14. } else {
  15. $this->renderHttpResponse($e);
  16. }
  17. }
  18. // 生成响应 web
  19. protected function renderHttpResponse(Throwable $e)
  20. {
  21. $this->getExceptionHandler()->render($this->app['request'], $e)->send();
  22. }
  23. // 获取系统绷定的异常处理类
  24. protected function getExceptionHandler()
  25. {
  26. return $this->app->make(ExceptionHandler::class);
  27. }

异常处理类

App\Exceptions\Handler 类

  1. public function report(Throwable $exception)
  2. {
  3. parent::report($exception);
  4. }
  5. public function render($request, Throwable $exception)
  6. {
  7. return parent::render($request, $exception);
  8. }

说明:

  • 主要的处理代码在该类的父类中函数
  • 这样处理是留出空余,使用者可以很方便的添加自己处理逻辑,提高框架的可扩展性

异常处理类的基类

Illuminate\Foundation\Exceptions\Handler , 该类是 laravel 处理异常的实现类

report 上报函数

laravel 系统上报部分主要是记录日志

  1. public function report(Throwable $e)
  2. {
  3. // 是否需要上报
  4. if ($this->shouldntReport($e)) {
  5. return;
  6. }
  7. // 异常类是否有自己的上报逻辑
  8. if (is_callable($reportCallable = [$e, 'report'])) {
  9. $this->container->call($reportCallable);
  10. return;
  11. }
  12. // 找到日志服务提供者类
  13. try {
  14. $logger = $this->container->make(LoggerInterface::class);
  15. } catch (Exception $ex) {
  16. throw $e;
  17. }
  18. // 记录日志
  19. $logger->error(
  20. $e->getMessage(),
  21. array_merge(
  22. $this->exceptionContext($e),
  23. $this->context(),
  24. ['exception' => $e]
  25. )
  26. );
  27. }

render 生成响应

  1. public function render($request, Throwable $e)
  2. {
  3. // 异常有自己的响应方法
  4. if (method_exists($e, 'render') && $response = $e->render($request)) {
  5. return Router::toResponse($request, $response);
  6. } elseif ($e instanceof Responsable) {
  7. return $e->toResponse($request);
  8. }
  9. // 包装一下异常类
  10. $e = $this->prepareException($e);
  11. // 特殊情况处理
  12. if ($e instanceof HttpResponseException) {
  13. return $e->getResponse();
  14. } elseif ($e instanceof AuthenticationException) {
  15. return $this->unauthenticated($request, $e);
  16. } elseif ($e instanceof ValidationException) {
  17. return $this->convertValidationExceptionToResponse($e, $request);
  18. }
  19. // 依据是否需要返回 json 数据,分两种情况进行处理
  20. return $request->expectsJson()
  21. ? $this->prepareJsonResponse($request, $e)
  22. : $this->prepareResponse($request, $e);
  23. }
  24. // 返回 json 数据
  25. protected function prepareJsonResponse($request, Throwable $e)
  26. {
  27. return new JsonResponse(
  28. $this->convertExceptionToArray($e),
  29. $this->isHttpException($e) ? $e->getStatusCode() : 500,
  30. $this->isHttpException($e) ? $e->getHeaders() : [],
  31. JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
  32. );
  33. }
  34. // 返回 web 数据
  35. protected function prepareResponse($request, Throwable $e)
  36. {
  37. // 是否返回详细报错信息
  38. if (! $this->isHttpException($e) && config('app.debug')) {
  39. return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
  40. }
  41. if (! $this->isHttpException($e)) {
  42. $e = new HttpException(500, $e->getMessage());
  43. }
  44. // 生成响应
  45. return $this->toIlluminateResponse(
  46. $this->renderHttpException($e), $e
  47. );
  48. }
  49. // 异常信息展示问题
  50. protected function renderHttpException(HttpExceptionInterface $e)
  51. {
  52. // 注册 blade 错误页面模板路径
  53. $this->registerErrorViewPaths();
  54. // 如果 blade 错误页面存在的话,使用 blade 进行处理
  55. if (view()->exists($view = $this->getHttpExceptionView($e))) {
  56. return response()->view($view, [
  57. 'errors' => new ViewErrorBag,
  58. 'exception' => $e,
  59. ], $e->getStatusCode(), $e->getHeaders());
  60. }
  61. // Symfony\Component\HttpFoundation\Response 自己的错误展示页面
  62. return $this->convertExceptionToResponse($e);
  63. }
  64. // 使用异常类信息,生成 Illuminate response 响应
  65. protected function toIlluminateResponse($response, Throwable $e)
  66. {
  67. if ($response instanceof SymfonyRedirectResponse) {
  68. $response = new RedirectResponse(
  69. $response->getTargetUrl(), $response->getStatusCode(), $response->headers->all()
  70. );
  71. } else {
  72. $response = new Response(
  73. $response->getContent(), $response->getStatusCode(), $response->headers->all()
  74. );
  75. }
  76. return $response->withException($e);
  77. }

测试

  1. routes\web.php 文件中:
  2. Route::get('/', function () {
  3. // throw new Exception('开始异常');
  4. echo 1/0;
  5. return '111';
  6. });

可以看到,页面返回错误信息,由于没有使用 blade 模板展示,样式略丑,而且将错误信息记录到 log 日志中了



评论 0

发表评论

Top