照猫画虎 实现 min-laravel 框架系列之异常
- laravel
- 2020-08-30
- 118
- 0
异常
异常捕获机制让我们可以更友好向客户端展示错误信息,laravel 在发生错误|异常时,通常会分两步走:
1、report:上报错误,主要是进行记录日志,发送报错邮件等
2、render:组织渲染携带报错|异常信息的影响给客户端
在生成异常响应时,仍然是建立在 symfony/http-foundation 库的基础上的,因此需要对这个包有一定的了解
异常处理整体祖册处理流程
服务提供者加载到系统
// Illuminate\Foundation\Http\Kernel::$bootstrappers 配置中protected $bootstrappers = [...\Illuminate\Foundation\Bootstrap\HandleExceptions::class,...];
各种异常处理函数注册 Illuminate\Foundation\Bootstrap\HandleExceptions::bootstrap()
异常处理类绑定
// app/bootstrap/app.php$app->singleton(Illuminate\Contracts\Debug\ExceptionHandler::class,App\Exceptions\Handler::class);
异常抛出时,由 App\Exceptions\Handler 类进行处理
异常服务提供者
Illuminate\Foundation\Bootstrap\HandleExceptions 类
注册处理函数
public function bootstrap(Application $app){// 预先占用系统一些内存self::$reservedMemory = str_repeat('x', 10240);$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');}}
异常处理函数 handleException
public function handleException(Throwable $e){try {// 预先流出的一块内存self::$reservedMemory = null;// 获取处理类,上报异常信息$this->getExceptionHandler()->report($e);} catch (Exception $e) {//}// 分 web|console 两种情况生成请求响应if ($this->app->runningInConsole()) {$this->renderForConsole($e);} else {$this->renderHttpResponse($e);}}// 生成响应 webprotected function renderHttpResponse(Throwable $e){$this->getExceptionHandler()->render($this->app['request'], $e)->send();}// 获取系统绷定的异常处理类protected function getExceptionHandler(){return $this->app->make(ExceptionHandler::class);}
异常处理类
App\Exceptions\Handler 类
public function report(Throwable $exception){parent::report($exception);}public function render($request, Throwable $exception){return parent::render($request, $exception);}
说明:
- 主要的处理代码在该类的父类中函数
- 这样处理是留出空余,使用者可以很方便的添加自己处理逻辑,提高框架的可扩展性
异常处理类的基类
Illuminate\Foundation\Exceptions\Handler , 该类是 laravel 处理异常的实现类
report 上报函数
laravel 系统上报部分主要是记录日志
public function report(Throwable $e){// 是否需要上报if ($this->shouldntReport($e)) {return;}// 异常类是否有自己的上报逻辑if (is_callable($reportCallable = [$e, 'report'])) {$this->container->call($reportCallable);return;}// 找到日志服务提供者类try {$logger = $this->container->make(LoggerInterface::class);} catch (Exception $ex) {throw $e;}// 记录日志$logger->error($e->getMessage(),array_merge($this->exceptionContext($e),$this->context(),['exception' => $e]));}
render 生成响应
public function render($request, Throwable $e){// 异常有自己的响应方法if (method_exists($e, 'render') && $response = $e->render($request)) {return Router::toResponse($request, $response);} elseif ($e instanceof Responsable) {return $e->toResponse($request);}// 包装一下异常类$e = $this->prepareException($e);// 特殊情况处理if ($e instanceof HttpResponseException) {return $e->getResponse();} elseif ($e instanceof AuthenticationException) {return $this->unauthenticated($request, $e);} elseif ($e instanceof ValidationException) {return $this->convertValidationExceptionToResponse($e, $request);}// 依据是否需要返回 json 数据,分两种情况进行处理return $request->expectsJson()? $this->prepareJsonResponse($request, $e): $this->prepareResponse($request, $e);}// 返回 json 数据protected function prepareJsonResponse($request, Throwable $e){return new JsonResponse($this->convertExceptionToArray($e),$this->isHttpException($e) ? $e->getStatusCode() : 500,$this->isHttpException($e) ? $e->getHeaders() : [],JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);}// 返回 web 数据protected function prepareResponse($request, Throwable $e){// 是否返回详细报错信息if (! $this->isHttpException($e) && config('app.debug')) {return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);}if (! $this->isHttpException($e)) {$e = new HttpException(500, $e->getMessage());}// 生成响应return $this->toIlluminateResponse($this->renderHttpException($e), $e);}// 异常信息展示问题protected function renderHttpException(HttpExceptionInterface $e){// 注册 blade 错误页面模板路径$this->registerErrorViewPaths();// 如果 blade 错误页面存在的话,使用 blade 进行处理if (view()->exists($view = $this->getHttpExceptionView($e))) {return response()->view($view, ['errors' => new ViewErrorBag,'exception' => $e,], $e->getStatusCode(), $e->getHeaders());}// Symfony\Component\HttpFoundation\Response 自己的错误展示页面return $this->convertExceptionToResponse($e);}// 使用异常类信息,生成 Illuminate response 响应protected function toIlluminateResponse($response, Throwable $e){if ($response instanceof SymfonyRedirectResponse) {$response = new RedirectResponse($response->getTargetUrl(), $response->getStatusCode(), $response->headers->all());} else {$response = new Response($response->getContent(), $response->getStatusCode(), $response->headers->all());}return $response->withException($e);}
测试
routes\web.php 文件中:Route::get('/', function () {// throw new Exception('开始异常');echo 1/0;return '111';});
可以看到,页面返回错误信息,由于没有使用 blade 模板展示,样式略丑,而且将错误信息记录到 log 日志中了