基於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 協議》,轉載必須註明作者和本文連結