Hôm nay chúng ta sẽ tìm hiểu handle exception trong laravel, giúp code gọn gàng, dễ dàng debug khi có lỗi phát sinh. Tất cả các exceptions sẽ được bắt và xử lý response trả về trong class App\Exceptions\Handler
không cần xử lý try, catch exception trong service, controller...
Tất cả các exception đều handle được trong class App\Exceptions\Handler
<?php
namespace App\Exceptions;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
//
];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
/**
* Render an exception into an HTTP response.
*
* @param Request $request is request
* @param Throwable $e is exception
*
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
*/
public function render($request, Throwable $e)
{
$statusCode = Response::HTTP_BAD_REQUEST;
if ($e->getCode() >= Response::HTTP_INTERNAL_SERVER_ERROR) {
$statusCode = $e->getCode();
}
$message = __('messages.errors.bad_request');
$errors = null;
switch (true) {
case $e instanceof AuthenticationException:
$message = __('messages.errors.unauthorized');
$statusCode = Response::HTTP_UNAUTHORIZED;
break;
case $e instanceof NotFoundHttpException:
$message = __('messages.errors.page_not_found');
$statusCode = Response::HTTP_NOT_FOUND;
break;
case $e instanceof ModelNotFoundException:
$message = __('messages.errors.model_not_found');
$statusCode = Response::HTTP_NOT_FOUND;
break;
case $e instanceof ApiException:
$message = $e->getMessage();
$statusCode = $e->getCode();
$errors = $e->getData();
break;
default:
break;
}
if ($request->is('*api*')) {
return $this->makeErrorResponse($statusCode, $message, $errors);
}
return response($message, Response::HTTP_BAD_REQUEST);
}
/**
* @param int $code
* @param string $message
* @param array|null $errors
* @param mixed|null $data
* @return Response
*/
protected function makeErrorResponse(int $code, string $message, ?array $errors = null, $data = null): Response
{
$response = [
'status' => false,
'message' => $message,
'data' => $errors
];
if (!empty($data)) {
$response['data'] = $data;
}
return response()->json($response, $code);
}
}
Tìm hiểu 1 số hàm và thuộc tính quan trọng trong class Handler
Khai báo log level cho excecption, ví dụ như chỉ xử lý PDOException với level CRITICAL
use PDOException;
use Psr\Log\LogLevel;
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
PDOException::class => LogLevel::CRITICAL,
];
Bỏ qua exception khai báo trong thuộc tính dontReport
use App\Exceptions\InvalidOrderException;
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
InvalidOrderException::class,
];
Hàm register()
là nơi đăng kí các exception mà bạn viết
use App\Exceptions\InvalidOrderException;
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
// Report exection sang 1 bên thứ 3
$this->reportable(function (InvalidOrderException $e) {
//
});
// Định nghĩa response trả về khi xảy ra lỗi
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
}
Trong bài vết này mình chỉ handle response trả về khi gặp lỗi lên mình sẽ không sử dụng hàm register, mà chỉ cần sử dụng hàm render()
để xử lý response trả về cho mọi exception.
/**
* Render an exception into an HTTP response.
*
* @param Request $request is request
* @param Throwable $e is exception
*
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
*/
public function render($request, Throwable $e)
{
$statusCode = Response::HTTP_BAD_REQUEST;
if ($e->getCode() >= Response::HTTP_INTERNAL_SERVER_ERROR) {
$statusCode = $e->getCode();
}
$message = __('messages.errors.bad_request');
$errors = null;
switch (true) {
case $e instanceof AuthenticationException:
$message = __('messages.errors.unauthorized');
$statusCode = Response::HTTP_UNAUTHORIZED;
break;
case $e instanceof NotFoundHttpException:
$message = __('messages.errors.page_not_found');
$statusCode = Response::HTTP_NOT_FOUND;
break;
case $e instanceof ModelNotFoundException:
$message = __('messages.errors.model_not_found');
$statusCode = Response::HTTP_NOT_FOUND;
break;
case $e instanceof ApiException:
$message = $e->getMessage();
$statusCode = $e->getCode();
$errors = $e->getData();
break;
default:
break;
}
if ($request->is('*api*')) {
return $this->makeErrorResponse($statusCode, $message, $errors);
}
return response($message, Response::HTTP_BAD_REQUEST);
}
Ở phần trên, chúng ta đã xử lý việc bắt và xử lý response trả về khi ứng dụng có exceptions, giờ chúng ta sẽ viết 1 ApiExecption
để định nghĩa các exectpions cơ bản của 1 api.
<?php
namespace App\Exceptions;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
class ApiException extends RuntimeException
{
/** @var mixed|null */
private $data;
/**
* ApiException constructor.
*
* @param int $httpCode
* @param string $message
* @param null $data
* @param Throwable|null $previous
*/
public function __construct(int $httpCode, string $message, $data = null, Throwable $previous = null)
{
$this->data = $data;
parent::__construct($message, $httpCode, $previous);
}
/**
* @return mixed|null
*/
public function getData()
{
return $this->data;
}
/**
* @param string $message
* @param null $data
* @return ApiException
*/
public static function serviceUnavailable(string $message = 'Service unavailable', $data = null): ApiException
{
return new ApiException(Response::HTTP_SERVICE_UNAVAILABLE, $message, $data);
}
/**
* @param string $message
* @param null $data
* @return ApiException
*/
public static function badRequest(string $message = "Bad request", $data = null): ApiException
{
return new ApiException(Response::HTTP_BAD_REQUEST, $message, $data);
}
/**
* @param string $message
* @param null $data
* @return ApiException
*/
public static function forbidden(string $message = "forbidden", $data = null): ApiException
{
return new ApiException(Response::HTTP_FORBIDDEN, $message, $data);
}
/**
* @param string $message
* @param null $data
* @return ApiException
*/
public static function notFound(string $message = "Not found", $data = null): ApiException
{
return new ApiException(Response::HTTP_NOT_FOUND, $message, $data);
}
/**
* @param string $message
* @param null $data
* @return ApiException
*/
public static function conflict(string $message = "Conflict", $data = null): ApiException
{
return new ApiException(Response::HTTP_CONFLICT, $message, $data);
}
/**
* @param string $message
* @param null $data
* @return ApiException
*/
public static function validation(string $message = "Request invalid", $data = null): ApiException
{
return new ApiException(Response::HTTP_UNPROCESSABLE_ENTITY, $message, $data);
}
}
Giờ chúng ta sẽ sử dụng ApiException vừa tạo nhé. Khai báo 1 route liệt kê tất cả các users
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\V1\UserController;
Route::group(['prefix' => 'v1'], function () {
Route::get('users', [UserController::class, 'index']);
});
Viết UserController để lấy danh sách các user.
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Exceptions\ApiException;
class UserController extends Controller
{
public function index()
{
throw ApiException::badRequest();
}
}
Chúng ta sẽ thử throw một badRequest() exception từ controller xem class Handler
có bắt được exception và trả về đúng response không? chúng ta sẽ dùng postman để check. Thật tuyệt vời, chúng ta đã xử lý được exception.
Như vậy là chúng ta đã handle được tất cả các exception trong ứng dụng sử dụng laravel, sử dụng cách này giúp chúng ta không bị lặp code khi thực hiện try, catch từ controller, giúp ứng dụng mạnh mẽ hơn rất nhiều rồi phải không nào? Thanks for reading...