Laravel Excpetions(错误处理) 源码分析
基于laravel10
分析
假如我们从控制器中抛出一个异常 就会被Pipeline管道
的prepareDestination
方法中的try
捕捉到,如果是那中捕捉不到的错误或者异常, 就会被注册的函数捕捉到(这个在前面的那个文章中有讲 博客:Laravel $bootstrappers数组加载源码分析(一)
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
//这里会捕获异常
return $this->handleException($passable, $e);
}
};
}
我们看一下handleException
方法做了什么事情
protected function handleException($passable, Throwable $e)
{
//这里是如果容器里面没有绑定这个服务 或者$passable不是requset实例
if (! $this->container->bound(ExceptionHandler::class) ||
! $passable instanceof Request) {
throw $e;
}
//通过容器实例化绑定的异常处理服务
$handler = $this->container->make(ExceptionHandler::class);
//这里就是去记录日志
$handler->report($e);
//这里就是去渲染输出响应到客户端
$response = $handler->render($passable, $e);
if (is_object($response) && method_exists($response, 'withException')) {
$response->withException($e);
}
return $this->handleCarry($response);
}
//这个服务在 bootstrap/app.php中绑定
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
我们看一下App\Exceptions\Handler
类的report
和render
方法做了什么处理
先看一下report
public function report(Throwable $e)
{
$e = $this->mapException($e);
//这里是是否不应该报告异常
if ($this->shouldntReport($e)) {
return;
}
①$this->reportThrowable($e);
}
//这个是异常类映射
protected function mapException(Throwable $e)
{
// 这里这个方法是laravel异常自带的方法 可能有一个异常被包装成了一个新的异常 但是报告异常需要用到真实的那个异常
if (method_exists($e, 'getInnerException') &&
($inner = $e->getInnerException()) instanceof Throwable) {
return $inner;
}
// 这里就是类映射 对这个异常类做一些处理 或者转换
foreach ($this->exceptionMap as $class => $mapper) {
if (is_a($e, $class)) {
return $mapper($e);
}
}
return $e;
}
protected function shouldntReport(Throwable $e)
{
// 不重复报告同一个异常 比如可能先report($e) 然后又throw这个异常 就会记录两次相同的异常
if ($this->withoutDuplicates && ($this->reportedExceptionMap[$e] ?? false)) {
return true;
}
// 这里就是不报告的异常类了 dontReport是给我们来定义哪些不需要报告 internalDontReport 是框架默认哪些异常不需要报告
$dontReport = array_merge($this->dontReport, $this->internalDontReport);
return ! is_null(Arr::first($dontReport, fn ($type) => $e instanceof $type));
}
①看一下reportThrowable
方法做了什么处理
protected function reportThrowable(Throwable $e): void
{
// 这里是标记一下这个异常已经报告了
$this->reportedExceptionMap[$e] = true;
// 这里是判断当前异常是否存在report方法 存在就调用 返回值不是false 就teturn了
if (Reflector::isCallable($reportCallable = [$e, 'report']) &&
$this->container->call($reportCallable) !== false) {
return;
}
// 这里是在register()方法中调用reportable()方法注册的
// 如果返回值是false 就return了
// 如果想返回false 可以链式调用stop()方法
foreach ($this->reportCallbacks as $reportCallback) {
if ($reportCallback->handles($e) && $reportCallback($e) === false) {
return;
}
}
// 下面就是去记录日志了
try {
$logger = $this->container->make(LoggerInterface::class);
} catch (Exception) {
throw $e;
}
// 这里就是一些特定的异常类指定特定的日志级别
$level = Arr::first(
$this->levels, fn ($level, $type) => $e instanceof $type, LogLevel::ERROR
);
$context = $this->buildExceptionContext($e);
method_exists($logger, $level)
? $logger->{$level}($e->getMessage(), $context)
: $logger->log($level, $e->getMessage(), $context);
}
report
方法就分析完了
接下來分析一下render
方法
public function render($request, Throwable $e)
{
// 这个方法report中已经分析了
$e = $this->mapException($e);
// 如果当前异常存在render方法
if (method_exists($e, 'render') && $response = $e->render($request)) {
return Router::toResponse($request, $response);
}
// 如果异常实现了Responsable接口
if ($e instanceof Responsable) {
return $e->toResponse($request);
}
// 这里是渲染异常前做准备 就是把一些异常类转换成新的异常类 统一做处理
$e = ②$this->prepareException($e);
// 这里是在register()方法中调用renderable()方法注册的 如果返回值为真 就返回响应
if ($response = $this->renderViaCallbacks($request, $e)) {
return $response;
}
// 这里就是对一些特定的异常做特定的相应
return match (true) {
$e instanceof HttpResponseException => $e->getResponse(),
$e instanceof AuthenticationException => $this->unauthenticated($request, $e),
$e instanceof ValidationException => $this->convertValidationExceptionToResponse($e, $request),
default => $this->renderExceptionResponse($request, $e),
};
}
protected function prepareException(Throwable $e)
{
return match (true) {
$e instanceof BackedEnumCaseNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
$e instanceof ModelNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
$e instanceof AuthorizationException && $e->hasStatus() => new HttpException(
$e->status(), $e->response()?->message() ?: (Response::$statusTexts[$e->status()] ?? 'Whoops, looks like something went wrong.'), $e
),
$e instanceof AuthorizationException && ! $e->hasStatus() => new AccessDeniedHttpException($e->getMessage(), $e),
$e instanceof TokenMismatchException => new HttpException(419, $e->getMessage(), $e),
$e instanceof SuspiciousOperationException => new NotFoundHttpException('Bad hostname provided.', $e),
$e instanceof RecordsNotFoundException => new NotFoundHttpException('Not found.', $e),
default => $e,
};
}
protected function renderViaCallbacks($request, Throwable $e)
{
foreach ($this->renderCallbacks as $renderCallback) {
//这里是是反射第一个参数的类型
foreach ($this->firstClosureParameterTypes($renderCallback) as $type) {
if (is_a($e, $type)) {
$response = $renderCallback($e, $request);
if (! is_null($response)) {
return $response;
}
}
}
}
}
以上就是异常处理大概流程了,如果有写的有误的地方,请大佬们指正
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 1年前 自动加精
面试官:你阅读过laravel的源码吗?
我:看过(论坛看别人)。