I'm trying to show a custom error page, which I'd like to appear if the error wasn't a 'page not found' or a authentication issue (e.g. trying to access a page which the user doesn't have access to). I'm using the code below in Laravel 5.3's Handler.php. While the 404 part works, the authentication part doesn't (triggering this error just returns the 500 page instead). What am I missing?
public function render($request, Exception $e)
{
if ($e instanceof NotFoundHttpException || $e instanceof AuthorizationException || $e instanceof AuthenticationException) {
return parent::render($request, $e);
}
else {
return response()->view('errors.500', [
'sentryID' => $this->sentryID,
], 500);
}
}
Edit : Looks like you want to handle all the global error pages. Laravel uses symfony's exception handler to generate the error page text and style. This can be found at
vendor/symfony/debug/ExceptionHandler.php
It's used in vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php as
use Symfony\Component\Debug\ExceptionHandler as SymfonyExceptionHandler;
To handle every error and exception you can extend the method prepareResponse to app/Exceptions/Handler.php and make appropriate changes.
protected function prepareResponse($request, Exception $e)
{
if ($this->isHttpException($e)) {
return $this->toIlluminateResponse($this->renderHttpException($e), $e);
} else {
return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
}
}
You can check the underlying working of this method in vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
End edit
You don't need to mess in the render method for this. Out of the box laravel searches for error views and renders them if available based on the error code. So for 404 and 500 you could just create the following two views and customize it in there.
resources/views/errors/404.blade.php
resources/views/errors/500.blade.php
This views get the exception, status and header information for you to display if needed. They are called like so
return response()->view("errors.{$status}", ['exception' => $e], $status, $e->getHeaders());
For the authentication check. Laravel calls the unauthenticated method in app/Exceptions/Handler.php when a user is unauthenticated. This code by default redirects the users to login page or shows a json response. You can make you changes here.
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login');
}
Related
I want to return a JSON response instead of the default 404 error page when ModelNotFoundException occurs. To do this, I wrote the following code into app\Exceptions\Handler.php :
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
However it doesn't work. When the ModelNotFoundException occurs, Laravel just shows a blank page. I find out that even declaring an empty render function in Handler.php makes Laravel display a blank page on ModelNotFoundException.
How can I fix this so it can return JSON/execute the logic inside the overriden render function?
In Laravel 8x, You need to Rendering Exceptions in register() method
use App\Exceptions\CustomException;
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->renderable(function (CustomException $e, $request) {
return response()->view('errors.custom', [], 500);
});
}
For ModelNotFoundException you can do it as below.
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
return response()->json(...);
});
}
By default, the Laravel exception handler will convert exceptions into an HTTP response for you. However, you are free to register a custom rendering Closure for exceptions of a given type. You may accomplish this via the renderable method of your exception handler. Laravel will deduce what type of exception the Closure renders by examining the type-hint of the Closure:
More info about the error exception
This code doesn't work for me (in Laravel 8.74.0):
$this->renderable(function (ModelNotFoundException$e, $request) {
return response()->json(...);
});
Don't know why, but ModelNotFoundException is directly forwarded to NotFoundHttpException (which is a part of Symfony Component) that used by Laravel and will ultimately triggers a 404 HTTP response. My workaround is checking the getPrevious() method of the exception:
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
if ($e->getPrevious() instanceof ModelNotFoundException) {
return response()->json([
'status' => 204,
'message' => 'Data not found'
], 200);
}
return response()->json([
'status' => 404,
'message' => 'Target not found'
], 404);
}
});
And then we will know that this exception come from ModelNotFoundException and return a different response with NotFoundHttpException.
Edit
This is why ModelNotFoundException thrown as NotFoundHttpException
This one is my Handler file:
use Throwable;
public function render($request, Throwable $exception)
{
if( $request->is('api/*')){
if ($exception instanceof ModelNotFoundException) {
$model = strtolower(class_basename($exception->getModel()));
return response()->json([
'error' => 'Model not found'
], 404);
}
if ($exception instanceof NotFoundHttpException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
}
}
This one is only for all request in API route. If you want to catch all request, so remove the first if.
Please note that by default Laravel emits a JSON representation of an exception ONLY when you send a request with the header parameter Accept: application/json! For all other requests, Laravel sends normal HTML rendered output.
I have a controller entry point where I execute another method from my ProductService inside a try catch block, I pretend to catch all exceptions that may occur inside $this->productService->create() method, except for Validation errors, if it's a validation error $e->getMessage() won't do, since I'll get generic response "Given data was invalid" instead of full custom messages. After reading some, I decided to use render method in laravel Handler class, I added this:
//In order to react to validation exceptions I added some logic to render method, but it won't actually work, I'm still getting normal exception message returned.
public function render($request, Exception $exception)
{
if ($request->ajax() && $exception instanceof ValidationException) {
return response()->json([
'message' => $e->errors(),
],422);
}
return parent::render($request, $exception);
}
However I'm still getting the default message, this means that my catch block is catching normal exception instead of my custom render method...
In my controller, try catch block looks like this:
try
{
$this->productService->create($request);
return response()->json([
'product' => $product,
], 200);
}
//I want to catch all exceptions except Validation fails here, and return simple error message to view as
json
catch (\Exception $e)
{
return response()->json([
'message' => $e->getMessage(),
], $e->getStatus() );
}
Also, in ValidationException, I cannot use $e->getCode, $e->getStatus(), it will always return 0 or sometimes 1, afaik it should be 422, that's why in my render method I'm manually returning 422. In my try catch block with normal Exceptions $e->getCode() works correctly, why is that?
In your render function, you are referencing an error instance that isn't defined, you have define Exception $exception but you are referencing $e->errors();
You code should be:
public function render($request, Exception $exception)
{
if ($request->ajax() && $exception instanceof ValidationException) {
return response()->json([
'message' => $exception->errors(),
],422);
}
return parent::render($request, $exception);
}
Change $e->errors(); to $exception->errors();
I'm developing a Laravel 5.6 API and I'm using Resources and Collections, Route Model Binding.
To show an item, I currently use following code in my controller:
public function show(Todo $todo)
{
TodoResource::withoutWrapping();
return new TodoResource($todo);
}
In the Exceptions > Handler.php I have the following:
public function render($request, Exception $exception)
{
// This will replace our 404 response with
// a JSON response.
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
This works perfectly when the item is found in the database. If the item is not in the database I get a (when using a browser):
"Sorry, the page you are looking for could not be found"
When using POSTMAN rest client, I'm getting
{
"message": "No query results for model [App\\Todo].",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
....
....
I would like to simply retrieve a 404 error with text "Resource not found", using both a browser or POSTMAN.
* Update with Routing info *
In my api.php, I have the following:
Route::apiResource('todos', 'TodoController');
Route::fallback(function () {
return response()->json(['message' => 'Not Found!'], 404);
});
In web.php, I have:
Route::Resource('todos', 'TodoController');
What is the best way to achieve this?
Make sure to alias the exception class you are checking for.
use Illuminate\Database\Eloquent\ModelNotFoundException;
Without this you are checking for an instance of App\Exceptions\ModelNotFoundException.
I'm using spatie permissions module for controlling roles and permissions within my site. I have added a bit to the Authenticate middleware. My Handle now looks like this:
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest())
{
if ($request->ajax() || $request->wantsJson())
return response('Unauthorized.', 401);
return redirect()->guest('login');
}
if ( ! Auth::user()->can('access acp') )
{
if ($request->ajax() || $request->wantsJson())
return response('Unauthorised.', 403);
abort(403, "You do not have permission to access the Admin Control Panel. If you believe this is an error please contact the admin who set your account up for you.");
}
return $next($request);
}
So if the user isn't logged in we send them to the login page, otherwise we check if the have permissions to access the acp, and if not show them a 403 error. I've added a 403.blade.php to the views/errors folder. However when I run that code I just get a Whoops! and the developer tools show a 500 ISE is being returned. I don't understand why I'm not seeing my custom error page.
So far I've tried switching the environment to production and turning debug mode off but that doesn't show the page. I've also tried throwing an authorisation exception but that doesn't do anything different. I also tried using App::abort() but again, I still got the 500 ISE.
I've tried Googling the issue but I can't find anyone else having this issue. I would really appreciate any help in getting this working.
Whoops returns
If I modify the code thusly
try
{
abort(403, "You do not have permission to access the Admin Control Panel. If you believe this is an error please contact the admin who set your account up for you.");
} catch ( HttpException $e )
{
dd($e);
}
then I get an instance of HttpException with my error code and message, so why isn't that then showing a custom error page?
I've managed to get around this problem with the the code below (note that it is a Lumen app but it should work with Laravel)
routes.php
$app->get('/test', function () use ($app) {
abort(403, 'some string from abort');
});
resources/views/errors/403.blade.php
<html>
<body>
{{$msg}}
<br>
{{$code}}
</body>
</html>
app/Exceptions/Handler.php, modify render() function as below
public function render($request, Exception $e)
{
if ($e instanceof HttpException) {
$statusCode = $e->getStatusCode();
if (view()->exists('errors.'.$statusCode)) {
return response(view('errors.'.$statusCode, [
'msg' => $e->getMessage(),
'code' => $statusCode
]), $statusCode);
}
}
return parent::render($request, $e);
}
It does what the Laravel should do according to docs
I make ajax requests to Laravel backend.
In backend I check request data and throw some exceptions.
Laravel, by default, generate html pages with exception messages.
I want to respond just raw exception message not any html.
->getMessage() doesn't work. Laravel, as always, generate html.
What shoud I do?
In Laravel 5 you can catch exceptions by editing the render method in app/Exceptions/Handler.php.
If you want to catch exceptions for all AJAX requests you can do this:
public function render($request, Exception $e)
{
if ($request->ajax()) {
return response()->json(['message' => $e->getMessage()]);
}
return parent::render($request, $e);
}
This will be applied to ANY exception in AJAX requests. If your app is sending out an exception of App\Exceptions\MyOwnException, you check for that instance instead.
public function render($request, Exception $e)
{
if ($e instanceof \App\Exceptions\MyOwnException) {
return response()->json(['message' => $e->getMessage()]);
}
return parent::render($request, $e);
}