Preface
After Laravel captures an exception globally, it will convert the exception into the corresponding data format and return it to the user. If we want the specified data format to correspond, we just need to rewrite the processing method after exception capture.
Exception handling process
The render method in Illuminate\Foundation\Exception\Handler is used to convert exceptions into responses.
public function render($request, Exception $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); } return $request->expectsJson() ? $this->prepareJsonResponse($request, $e) : $this->prepareResponse($request, $e); }
prepareException() is called in render() to preprocess some exceptions, but the operation converted into response is not performed.
ModelNotFoundException is generally not thrown when the model is found. In prepareException(), it is converted to NotFoundHttpException in the Symfony package, with the default status code 404;
AuthorizationException is thrown when the Policy permission fails to pass. In prepareException(), it is converted to AccessDeniedHttpException in the Symfony package, with default status code 403;
TokenMismatchException is thrown when the CSRF verification fails. In prepareException(), it is converted to HttpException in the Symfony package, given status code 419;
Other exceptions are returned directly.
protected function prepareException(Exception $e) { if ($e instanceof ModelNotFoundException) { $e = new NotFoundHttpException($e->getMessage(), $e); } elseif ($e instanceof AuthorizationException) { $e = new AccessDeniedHttpException($e->getMessage(), $e); } elseif ($e instanceof TokenMismatchException) { $e = new HttpException(419, $e->getMessage(), $e); } return $e; }
After returning to render() and pre-processing the exception, HttpResponseException, AuthenticationException and ValidationException are processed separately, and converted to response and returned.
Exceptions other than this are handled in prepareJsonResponse() or prepareResponse(). ExpectsJson() is used to determine whether to return a json response or a normal response.
Modify exception response format
After understanding the exception handling process, the exception response format will be processed.
Modify the login authentication exception format
As can be seen from the above, after AuthenticationException is captured, unauthenticated() is called to handle it.
protected function unauthenticated($request, AuthenticationException $exception) { return $request->expectsJson() ? response()->json(['message' => $exception->getMessage()], 401) : redirect()->guest($exception->redirectTo() ?? route('login')); }
Rewrite unauthenticated() in to return the data format we want.
protected function unauthenticated($request, AuthenticationException $exception) { return $request->expectsJson() ? response()->json([ 'code' => 0, 'data' => $exception->getMessage(), ], 401) : redirect()->guest($exception->redirectTo() ?? route('login')); }
Modify the verification exception format
As we can also see from the above, ValidationException is captured and handed over to convertValidationExceptionToResponse() to process. After entering this method, we need to continue tracking. If a json response is required, it will be handed over to invalidJson() to process.
protected function convertValidationExceptionToResponse(ValidationException $e, $request) { if ($e->response) { return $e->response; } return $request->expectsJson() ? $this->invalidJson($request, $e) : $this->invalid($request, $e); }
protected function invalidJson($request, ValidationException $exception) { return response()->json([ 'message' => $exception->getMessage(), 'errors' => $exception->errors(), ], $exception->status); }
We continue to rewrite invalidJson() to customize the return format.
protected function invalidJson($request, ValidationException $exception) { return response()->json([ 'code' => 0, 'data' => $exception->errors(), ], $exception->status); }
Modify other exception formats
Other exceptions are called prepareJsonResponse() to handle, which in turn calls convertExceptionToArray() to handle the response format.
protected function prepareJsonResponse($request, Exception $e) { return new JsonResponse( $this->convertExceptionToArray($e), $this->isHttpException($e) ? $e->getStatusCode() : 500, $this->isHttpException($e) ? $e->getHeaders() : [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); }
protected function convertExceptionToArray(Exception $e) { return config('') ? [ 'message' => $e->getMessage(), 'exception' => get_class($e), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => collect($e->getTrace())->map(function ($trace) { return Arr::except($trace, ['args']); })->all(), ] : [ 'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error', ]; }
Rewrite convertExceptionToArray() in Customize other exception response formats.
protected function convertExceptionToArray(Exception $e) { return config('') ? [ 'code' => 0, 'data' => $e->getMessage(), 'exception' => get_class($e), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => collect($e->getTrace())->map(function ($trace) { return Arr::except($trace, ['args']); })->all(), ] : [ 'code' => 0, 'data' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error', ]; }
Force json response
ExpectsJson() appears many times in the code. This method is used to determine whether to return a json response or a normal response.
public function expectsJson() { return ($this->ajax() && ! $this->pjax() && $this->acceptsAnyContentType()) || $this->wantsJson(); }
Under the following two conditions, a json response will be returned.
Non-XML requests, non-pjax, and Accept in Headers is set to receive all format responses;
Headers Accept is set to /json, +json. For example: Accept:application/json.
Other than that, no response to json will be performed. We can use middleware to force append Accept:application/json to make json return when the exception is responding. (Refer to the methods mentioned in tutorial L03 6.0)
Create middleware AcceptHeader
<?php namespace App\Http\Middleware; use Closure; class AcceptHeader { public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); } }
In app/Http/, just add the middleware to the routing group.
protected $middlewareGroups = [ 'web' => [ . . . 'api' => [ \App\Http\Middleware\AcceptHeader::class, 'throttle:60,1', 'bindings', ], ];
The mission is done.
Summarize
This is the article about how Laravel implements the exception handling response format suitable for API. For more information about Laravel's exception handling response format suitable for API, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!