照猫画虎 实现 min-laravel 框架系列之异常
- laravel
- 2020-08-30
- 4032
- 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);
}
}
// 生成响应 web
protected 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 日志中了