Anyone know what is the best way to handle errors in Laravel, there is any rules or something to follow ?
Currently i\'m doing this :
public function st
I think it would be better to modify existing behaviour implemented in app/Exceptions/Handler.php than overriding it.
You can modify JSONResponse returned by parent::render($request, $exception);
and add/remove data.
Example implementation:
app/Exceptions/Handler.php
use Illuminate\Support\Arr;
// ... existing code
public function render($request, Exception $exception)
{
if ($request->is('api/*')) {
$jsonResponse = parent::render($request, $exception);
return $this->processApiException($jsonResponse);
}
return parent::render($request, $exception);
}
protected function processApiException($originalResponse)
{
if($originalResponse instanceof JsonResponse){
$data = $originalResponse->getData(true);
$data['status'] = $originalResponse->getStatusCode();
$data['errors'] = [Arr::get($data, 'exception', 'Something went wrong!')];
$data['message'] = Arr::get($data, 'message', '');
$originalResponse->setData($data);
}
return $originalResponse;
}
Laravel is already able to manage json responses by default.
Withouth customizing the render method in app\Handler.php you can simply throw a Symfony\Component\HttpKernel\Exception\HttpException, the default handler will recognize if the request header contains Accept: application/json and will print a json error message accordingly.
If debug mode is enabled it will output the stacktrace in json format too.
Here is a quick example:
<?php
...
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiController
{
public function myAction(Request $request)
{
try {
// My code...
} catch (\Exception $e) {
throw new HttpException(500, $e->getMessage());
}
return $myObject;
}
}
Here is laravel response with debug off
{
"message": "My custom error"
}
And here is the response with debug on
{
"message": "My custom error",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException",
"file": "D:\\www\\myproject\\app\\Http\\Controllers\\ApiController.php",
"line": 24,
"trace": [
{
"file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
"line": 48,
"function": "myAction",
"class": "App\\Http\\Controllers\\ApiController",
"type": "->"
},
{
"file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
"line": 212,
"function": "dispatch",
"class": "Illuminate\\Routing\\ControllerDispatcher",
"type": "->"
},
...
]
}
Using HttpException the call will return the http status code of your choice (in this case internal server error 500)
Try this, i have used it in my project (app/Exceptions/Handler.php)
public function render($request, Exception $exception)
{
if ($request->wantsJson()) { //add Accept: application/json in request
return $this->handleApiException($request, $exception);
} else {
$retval = parent::render($request, $exception);
}
return $retval;
}
Now Handle Api exception
private function handleApiException($request, Exception $exception)
{
$exception = $this->prepareException($exception);
if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
$exception = $exception->getResponse();
}
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
$exception = $this->unauthenticated($request, $exception);
}
if ($exception instanceof \Illuminate\Validation\ValidationException) {
$exception = $this->convertValidationExceptionToResponse($exception, $request);
}
return $this->customApiResponse($exception);
}
After that custom Api handler response
private function customApiResponse($exception)
{
if (method_exists($exception, 'getStatusCode')) {
$statusCode = $exception->getStatusCode();
} else {
$statusCode = 500;
}
$response = [];
switch ($statusCode) {
case 401:
$response['message'] = 'Unauthorized';
break;
case 403:
$response['message'] = 'Forbidden';
break;
case 404:
$response['message'] = 'Not Found';
break;
case 405:
$response['message'] = 'Method Not Allowed';
break;
case 422:
$response['message'] = $exception->original['message'];
$response['errors'] = $exception->original['errors'];
break;
default:
$response['message'] = ($statusCode == 500) ? 'Whoops, looks like something went wrong' : $exception->getMessage();
break;
}
if (config('app.debug')) {
$response['trace'] = $exception->getTrace();
$response['code'] = $exception->getCode();
}
$response['status'] = $statusCode;
return response()->json($response, $statusCode);
}
Always add Accept: application/json
in your api or json request.
Using some code from @RKJ best answer I have handled the errors in this way:
Open "Illuminate\Foundation\Exceptions\Handler" class and search for a method named "convertExceptionToArray". This method converts the HTTP exception into an array to be shown as a response. In this method, I have just tweaked a small piece of code that will not affect loose coupling.
So replace convertExceptionToArray method with this one
protected function convertExceptionToArray(Exception $e, $response=false)
{
return config('app.debug') ? [
'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) ? ($response ? $response['message']: $e->getMessage()) : 'Server Error',
];
}
Now navigate to the App\Exceptions\Handler class and paste the below code just above the render method:
public function convertExceptionToArray(Exception $e, $response=false){
if(!config('app.debug')){
$statusCode=$e->getStatusCode();
switch ($statusCode) {
case 401:
$response['message'] = 'Unauthorized';
break;
case 403:
$response['message'] = 'Forbidden';
break;
case 404:
$response['message'] = 'Resource Not Found';
break;
case 405:
$response['message'] = 'Method Not Allowed';
break;
case 422:
$response['message'] = 'Request unable to be processed';
break;
default:
$response['message'] = ($statusCode == 500) ? 'Whoops, looks like something went wrong' : $e->getMessage();
break;
}
}
return parent::convertExceptionToArray($e,$response);
}
Basically, we overrided convertExceptionToArray method, prepared the response message, and called the parent method by passing the response as an argument.
Note: This solution will not work for Authentication/Validation errors but most of the time these both errors are well managed by Laravel with proper human-readable response messages.
In my opinion I'd keep it simple.
Return a response with the HTTP error code and a custom message.
return response()->json(['error' => 'You need to add a card first'], 500);
Or if you want to throw a caught error you could do :
try {
// some code
} catch (Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
You can even use this for sending successful responses:
return response()->json(['activeSubscription' => $this->getActiveSubscription()], 200);
This way no matter which service consumes your API it can expect to receive the same responses for the same requests.
You can also see how flexible you can make it by passing in the HTTP status code.
In your handler.php This should work for handling 404 Exception.
public function render($request, Throwable $exception ){
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Data not found'
], 404);
}
return parent::render($request, $exception);
}