How to create different view for different exception laravel 5? - laravel

I want to set different view blade template for different exception and also pass the errors on the following page. I tried out the following code but it's not working. It always goes to the else portion of the code and run the parent::render($request, $e);code.
public function render($request, Exception $e)
{
if ($this->isHttpException($e))
{
if($e instanceof InvalidArgumentException)
{
return response()->view('front.missing', [], 404);
}elseif($e instanceof ErrorException){
return response()->view('front.missing2', [], 404);
}
return $this->renderHttpException($e);
}else{
if($e instanceof InvalidArgumentException)
{
return response()->view('errors.204', []);
}
return parent::render($request, $e);
}
}
Where is the problem here and what I will do now?

InvalidArgumentException is not a child class of HttpException, so it always goes to the else portion.

Related

Error logging is truncated in Laravel of Guzzle http

Guzzle http is truncating exceptions with more than 120 characters, but I need to log the full exception message. How can I do this?
I am using laravel 4.2.22.
try {
// whatever
} catch (\GuzzleHttp\Exception\RequestException $ex) {
return $ex->getResponse()->getBody()->getContents();
// you can even json_decode the response like json_decode($ex->getResponse()->getBody()->getContents(), true)
}
It is the same for Laravel 5 and 4
try {
$response = $client->post($path, $params);
} catch (\GuzzleHttp\Exception\RequestException $ex) {
\Log::debug('error');
\Log::debug((string) $ex->getResponse()->getBody());
throw $ex;
}
if you just go to $ex->getMessage(), you will get
(truncated...)
at the end.
Might be better solution:
try {
// do request here like:
// return $client->post($path, $params);
} catch (\GuzzleHttp\Exception\ServerException $ex) {
$exFactoryWithFullBody = new class('', $ex->getRequest()) extends \GuzzleHttp\Exception\RequestException {
public static function getResponseBodySummary(ResponseInterface $response)
{
return $response->getBody()->getContents();
}
};
throw $exFactoryWithFullBody->create($ex->getRequest(), $ex->getResponse());
}

Laravel API, how to properly handle errors

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 store(Request $request)
{
$plate = Plate::create($request->all());
if ($plate) {
return $this->response($this->plateTransformer->transform($plate));
} else {
// Error handling ?
// Error 400 bad request
$this->setStatusCode(400);
return $this->responseWithError("Store failed.");
}
}
And the setStatusCode and responseWithError come from the father of my controller :
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
return $this;
}
public function responseWithError ($message )
{
return $this->response([
'error' => [
'message' => $message,
'status_code' => $this->getStatusCode()
]
]);
}
But is this a good way to handle the API errors, i see some different way to handle errors on the web, what is the best ?
Thanks.
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.
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)
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.
If you are using Laravel 8+, you can do it simply by adding these lines in Exception/Handler.php on register() method
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
For me, the best way is to use specific Exception for API response.
If you use Laravel version > 5.5, you can create your own exception with report() and render() methods. Use command:
php artisan make:exception AjaxResponseException
It will create AjaxResponseException.php at: app/Exceptions/
After that fill it with your logic. For example:
/**
* Report the exception.
*
* #return void
*/
public function report()
{
\Debugbar::log($this->message);
}
/**
* Render the exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #return JsonResponse|Response
*/
public function render($request)
{
return response()->json(['error' => $this->message], $this->code);
}
Now, you can use it in your ...Controller with try/catch functionality.
For example in your way:
public function store(Request $request)
{
try{
$plate = Plate::create($request->all());
if ($plate) {
return $this->response($this->plateTransformer->transform($plate));
}
throw new AjaxResponseException("Plate wasn't created!", 404);
}catch (AjaxResponseException $e) {
throw new AjaxResponseException($e->getMessage(), $e->getCode());
}
}
That's enough to make your code more easier for reading, pretty and useful.
Best regards!
For Laravel 8+ in file App\Exceptions\Hander.php inside method register() paste this code:
$this->renderable(function (Throwable $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => $e->getMessage(),
'code' => $e->getCode(),
], 404);
}
});
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;
}
Well, all answers are ok right now, but also they are using old ways.
After Laravel 8, you can simply change your response in register() method by introducing your exception class as renderable:
<?php
namespace Your\Namespace;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
}
}
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 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);
}
You don't have to do anything special. Illuminate\Foundation\Exceptions\Handler handles everything for you. When you pass Accept: Application/json header it will return json error response. if debug mode is on you will get exception class, line number, file, trace if debug is off you will get the error message. You can override convertExceptionToArray. Look at the default implementation.
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) ? $e->getMessage() : 'Server Error',
];
As #shahib-khan said,
this happens in debug mode and is handled by Laravel in production mode.
you can see base method code in
\Illuminate\Foundation\Exceptions\Handler::convertExceptionToArray
protected function convertExceptionToArray(Throwable $e)
{
return config('app.debug') ? [
'message' => $e->getMessage(),
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->map(fn ($trace) => Arr::except($trace, ['args']))->all(),
] : [
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
];
}
Therefore, I overrode the the convertExceptionToArray function in app/Exceptions/Handler
of course, still in debug mode, if the exception is thrown, you can track it in the Laravel.log.
protected function convertExceptionToArray(Throwable $e)
{
return [
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
];
}
Add header to your API endpoint. which works for me. it will handle the error request properly.
Accept: application/json

How to add custom error in Laravel?

How to call custmo error in controller, that after to show in template?
For example, I have condition:
if($id == 500){
// call error
}
If you want to call just the error in the controller and abort, you do
abort(500, 'Internal error');
If you want to return an error
return redirect()->back()->withErrors(['error' => 'was 500']);
You can do this by catching the error in the Handler (app/Exceptions/Handler.php), something like this:
public function render($request, Exception $e){
// other errors here by your wish
// custom error message
if ($e instanceof \ErrorException) {
return response()->view('errors.500', [], 500);
}else{
return parent::render($request, $e);
}
return parent::render($request, $e);
}
Note that you need 500 template in resources/views/errors/500.blade.php (create if it doesnt exist, and fill if with your data or iformation about the exception)

Custom 404 page in Lumen

I'm new to Lumen and want to create an app with this framework. Now I have the problem that if some user enters a wrong url => http://www.example.com/abuot (wrong) => http://www.example.com/about (right), I want to present a custom error page and it would be ideal happen within the middleware level.
Furthermore, I am able to check if the current url is valid or not, but I am not sure how can I "make" the view within the middleware, the response()->view() won't work.
Would be awesome if somebody can help.
Seeing as errors are handled in App\Exceptions\Handler, this is the best place to deal with them.
If you are only after a custom 404 error page, then you could do this quite easily:
Add this line up the top of the Handler file:
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Alter the render function to look like so:
public function render($request, Exception $e)
{
if($e instanceof NotFoundHttpException){
return response(view("errors.404"), 404);
}
return parent::render($request, $e);
}
This assumes your custom 404 page is stored in an errors folder within your views, and will return the custom error page along with a 404 status code.
You may want to add this so that when for example blade blows up the error page hander will not throw a PHP error.
public function render($request, Exception $exception)
{
if (method_exists('Exception','getStatusCode')){
if($exception->getStatusCode() == 404){
return response(view("errors.404"), 404);
}
if($exception->getStatusCode() == 500){
return response(view("errors.500"), 404);
}
}
return parent::render($request, $exception);
}
I am using Lumen 8.x version and below solution worked for me:
File path: ‎⁨▸ ⁨app⁩ ▸ ⁨Exceptions⁩ ▸ ⁨Handler.php
public function render($request, Throwable $exception)
{
// start custom code
if($exception->getStatusCode() == 404){
return response(view("errors.404"), 404);
}
if($exception->getStatusCode() == 500){
return response(view("errors.500"), 404);
}
// end custom code
return parent::render($request, $exception);
}
Do not forget to create errors folder at /resources/views/errors and create the below 2 new files in errors folder:
404.blade.php
500.blade.php
and add html tags and messages in those files what you want to add.
Happy to help you. Thanks for asking this question.
I faced the same situation. response(view("errors.404"), 404)did not work for me, so I changed it as follows:
public function render($request, Exception $exception)
{
if($exception instanceof NotFoundHttpException){
return response(view('errors.404')->render(), 404);
}
return parent::render($request, $exception);
}

Throw an error in Laravel (not eloquent)

In my package I perform a check on a user id:
//check
if(!$this->checkId($id)) //error
If this fails I need to throw an error as the method in my package will fail to work and I need to inform the user.
Please note, this is not a eloquent query so I do not need any find or fail methods.
How can I do this in laravel?
I agree with the previous answer, but I would throw an exception from checkId() method - since either check passes or fails (and throws exception).
class CheckIdException extends Exception
{
}
class WhateverClass
{
public function checkId($id)
{
// do the check
$passes = ....
if (! $passes) {
throw new CheckIdException('CheckId() failed');
}
return true;
}
}
// somewhere in the app code
try {
$this->checkId($id);
} catch (CheckIdException $e) {
return Response::json(['error' => 'checkId', 'message' => 'meaningul error description']);
} catch (Exception $e) {
return Response::json(['error' => 'UnknownError', 'message' => $e->getMessage()]);
}
// yay, ID check passes! Continue!
...so just throw an error?
if(!$this->checkId($id)) //error
{
App::abort(500, 'CheckId() failed');
}
or
if(!$this->checkId($id)) //error
{
throw new Exception("CheckId() failed");
}

Resources