Back

Laravel 5 custom error view for 500


We can put custom error views in resources/templates/error/ERRORCODE.blade.php. This works, except for 500. Bug or by design?

mauserrifle replied 4 years ago

Anyone?

GabLeRoux replied 4 years ago

Had same problem, not quite sure why this happens. Maybe we need to specify something special for 500 error in inscription/app/Exceptions/Handler.php?

I found Mat Stauffer's post saying it works with 404.blade.php, but no word on 500.blade.php

On production with DEBUG=false, I still get that Laravel default page laravel5-500-error-on-production.png

kkiernan replied 4 years ago

This test works for me in Laravel 5. Navigating to the route loads my views/errors/500.blade.php view. Placing the error view in any other directory does not work for me by default.

Route::get('500', function()
{
    abort(500);
});
browner12 replied 4 years ago

so the problem is that Laravel will only do this automatic rendering of error pages for exceptions that are instances of HttpException. unfortunately when your server throws an error (method does not exist, variable undefined, etc) it actually throws a FatalErrorException. as such, it is uncaught, and trickles down to the SymfonyDisplayer() which either gives you the trace (debug true) or ugly one liner 'Whoops, looks like something went wrong' (debug false).

the reason abort(500) works is that it specifically throws an HttpException with a status code of 500.

if anyone has come up with a good solution to this, i would love to hear it. for now I'm specifically catching FatalErrorExceptions in my handler.

astroanu replied 4 years ago

Yes an HttpException must be thrown to show the view.

JeremyHutchings replied 4 years ago

browner12 said:

..... for now I'm specifically catching FatalErrorExceptions in my handler.

Are you using instanceof to do that ? I'm trying to build a custom page for FatalErrorException. Having the default Whoops page for production is unacceptable.

sdebacker replied 4 years ago

There is not only FatalErrorException, but also RuntimeException and other, so here is my render method that catch any server errors (file app/Exceptions/Handler.php) :

public function render($request, Exception $e)
{

    // 404 page when a model is not found
    if ($e instanceof ModelNotFoundException) {
        return response()->view('errors.404', [], 404);
    }

    if ($this->isHttpException($e)) {
        return $this->renderHttpException($e);
    } else {
        // Custom error 500 view on production
        if (app()->environment() == 'production') {
            return response()->view('errors.500', [], 500);
        }
        return parent::render($request, $e);
    }

}
sdebacker replied 4 years ago

For Laravel 5.1 :

public function render($request, Exception $e)
{

    // 404 page when a model is not found
    if ($e instanceof ModelNotFoundException) {
        return response()->view('errors.404', [], 404);
    }

    // Custom error 500 view on production
    if (app()->environment() == 'production') {
        return response()->view('errors.500', [], 500);
    }

    return parent::render($request, $e);

}
davidnguyen replied 4 years ago

If anyone is wondering how to catch it, use:

if($e instanceof \Symfony\Component\Debug\Exception\FatalErrorException) {
	$statusCode = 500;
}
zlatevbg replied 3 years ago

I have tried the above code but i didn't work when the Exception is of type \PDOException and the DB server is unavailable or the credential details are wrong:

if ($e instanceof \PDOException) {
    // This is not working when database is unavailable or the login details are wrong...
    return response()->view('errors.404', ['message', $e->getMessage()], 404);
}
Alexander replied 3 years ago

For me this worked in Laravel 5.2

app/Exceptions/Handler.php

public function render($request, Exception $e)
{
    // ... other checks like for TokenMismatchException

    // custom error message
    if ($e instanceof \ErrorException) {
        return response()->view('errors.500', [], 500);
    } else {
        return parent::render($request, $e);
    }

}
adampatterson replied 3 years ago

Not to beat a dead horse, For some reason none of the above solutions worked for me.

Here is my solution.

app/Exceptions/Handler.php

Add this to your render method.

https://gist.github.com/adampatterson/e86ac67d321932ecd787f060ae6bdb81

Erse replied 3 years ago

When adding custom 500 error, if user makes mistake instead of showing validation it throws them to 500 error view.

Anybody had same problem?

public function render($request, Exception $e)
    {
        if (config('app.debug') || parent::isHttpException($e)) {
            return parent::render($request, $e);
        }

        return response()->view("errors.500", [], 500);
    }
fabiancz replied 3 years ago

I have same problem as Erse.

public function render($request, Exception $e)
{
    $exception = \Symfony\Component\Debug\Exception\FlattenException::create($e);
    $statusCode = $exception->getStatusCode($exception);

    if (env('APP_DEBUG') == FALSE && $statusCode == 500 && $e instanceof ValidationException != TRUE) {
        return response()->view('errors.500', [], 500);
    } else {
        return parent::render($request, $e);
    }
}

My solution is test instance of $e, if it is ValidationException.

Is there any other type of exception, which shouldn't be catch by error page?

matthewjames replied 3 years ago

@erse and @fabiancz, if you look at the parent render method you can see what exceptions it catches (link). There are a couple more like ModelNotFoundException and AuthorizationException.

Since the handler extends the parent handler, I just override the method that builds the symfony response for non-http exceptions. That way the existing handler still handles any HTTP exceptions, validation exceptions etc. If in debug mode I still use the original method since it has stacktrace etc.

namespace App\Exceptions;

class Handler extends ExceptionHandler
{
    // existing code up here...
    
    protected function convertExceptionToResponse(Exception $e)
    {
        if (config('app.debug')) {
            return parent::convertExceptionToResponse($e);
        }

        return response()->view('errors.500', [], 500);
    }
}
sevenpointsix replied 2 years ago

This solution (from @matthewjames) worked really well for me. The only problem I had was that my 500 page view was repeated multiple times on screen, if there were multiple errors on a page.

I resolved this by adding this an ob_clean() to the code, as follows:

protected function convertExceptionToResponse(Exception $e)
{
	if (config('app.debug')) {
		return parent::convertExceptionToResponse($e);
	}
	ob_clean();
	return response()->view('errors.500', [], 500);
}

This feels like a clunky fix, though -- there may be a much better approach.

joaorobertopb replied 2 years ago

Hi guys ,

My solution ->

public function render($request, Exception $exception)
    {
        if ($exception instanceof \ErrorException) {
            $response = response()->view('errors.500', [], 500);
            return  $this->toIlluminateResponse($response,$exception);
        }

        return parent::render($request, $exception);
    }
gart77 replied 2 years ago

I've put together your solutions, with additional condition: In my app user not logged in is not allowed to see any other page than login/register.


public function render($request, Exception $e)
{

    // Render full exception details in debug mode
    if(config('app.debug')) {
        return parent::render($request, $e);
    }

    // Redirect if token mismatch error
    // Usually because user stayed on the same page too long and his session expired
    if ($e instanceof TokenMismatchException) {

        // if it's ajax request send back "unauthorized" status response
        // then in js/jQuery you catch and redirect all those 401 responses:
        // $.ajaxSetup({ statusCode: { 401: function() { window.location.href = '/your_login_page'; } } });
        if ($request->ajax()){
            return response('Token Mismatch Exception', 401);
        }

        // redirect to login page
        return redirect()->route('auth.login.get');
    }

    // Model not found
    if ($e instanceof ModelNotFoundException) {

        // it could be useful to get not founded model name and pass it to 404 error page:
        // $model = $e->getModel();
        // $baseModel = new $model;
        // $resourceNotFounded = class_basename($baseModel);
        // return response()->view('app.errors.404', compact('resourceNotFounded'), 404);


        // or simply redirect to not found page
        return response()->view('app.errors.404', [], 404);
    }

    // Http exceptions
    if ($this->isHttpException($e))
    {
        // redirect to login if not authenticated
        // I don't want to let unauthenticated users to see error pages
        if( ! \Auth::check() ){
            return redirect()->route('auth.login.get');
        }


        // try to find right error page for this exception
        // I have prepared 4 most common errors: 403,404, 500 and 503
        $exception = FlattenException::create($e);
        $statusCode = $exception->getStatusCode($exception);

        if (in_array($statusCode, array(403, 404, 500, 503))){
            return response()->view('app.errors.' . $statusCode, [], $statusCode);
        }
    }

    // uncomment line below if you want 500 error page for all other errors
    // I prefer to use default behavior for them
    //return response()->view('app.errors.500', [], 500);

    return parent::render($request, $e);
}

Used classes have to be imported with "use", or written with full path.

gjrdiesel replied 2 years ago

gart77 that is awesome, thank you!

ennio21 replied 1 year ago

I pen a package to get error pages. You can check here https://github.com/enniosousa/server-error-pages


Sign in to participate in this thread!



We'd like to thank these amazing companies for supporting us