Tutorial

Creating A Laravel 404 Page Using Custom Exception Handlers

Draft updated on Invalid Date
author

John Kariuki

Creating A Laravel 404 Page Using Custom Exception Handlers

This tutorial is out of date and no longer maintained.

Introduction

PHP Exceptions are thrown when an unprecedented event or error occurs. As a rule of thumb, an exception should not be used to control the application logic such as if-statements and should be a subclass of the Exception class.

Being unprecedented, an exception can be thrown at any point or time of our application.

Laravel provides a convenient exception handler class that checks for all exceptions thrown in a Laravel application and gives relevant responses. This is made possible by the fact that all Exceptions used in Laravel extend the Exception class.

One main advantage of having all exceptions caught by a single class is that we are able to create custom exception handlers that return different response messages depending on the exception.

In this tutorial, we will look at how to create a custom exception handler in Laravel 5.2 and how to return a 404 page depending on the Exception.

How it Works

In Laravel 5.2, all errors and exceptions, both custom and default, are handled by the Handler class in app/Exceptions/Handler.php with the help of two methods.

  • report()

The report method enables you to log raised exceptions or parse them to error logging engines such as bugsnag or sentry which we will not delve into in this tutorial.

  • render()

The render method responds with an error message raised by an exception. It generates an HTTP response from the exception and sends it back to the browser.

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
        return parent::render($request, $e);
    }

We can however override the default error handling with our own custom exception handler.

/**
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $e
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $e)
{
    if ($e instanceof CustomException) {
        return response()->view('errors.custom', [], 500);
    }

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

Under the hood, Laravel does its own handling checks to determine the best possible response for an exception. Taking a look at the parent class (Illuminate\Foundation\Exceptions\Handler), the render method generates a different response depending on the thrown Exception.

    /**
     * Render an exception into a response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function render($request, Exception $e)
    {
        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof ModelNotFoundException) {
            $e = new NotFoundHttpException($e->getMessage(), $e);
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof AuthorizationException) {
            $e = new HttpException(403, $e->getMessage());
        } elseif ($e instanceof ValidationException && $e->getResponse()) {
            return $e->getResponse();
        }

        if ($this->isHttpException($e)) {
            return $this->toIlluminateResponse($this->renderHttpException($e), $e);
        } else {
            return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
        }
    }

Throwing a Laravel Eloquent Exception

In this section, we will create an inbuilt Laravel error by intentionally raising an exception.

To do this, we will try to fetch records that do not exist from a model using the firstOrFail() Eloquent method.

Go ahead and set up a simple SQLite database. Luckily, Laravel ships with a User model and a corresponding users table. Simply do the following.

  1. Create a new Laravel project.
  2. Update your .env file to have DB_CONNECTION to be sqlite and the only database parameter.
  3. Create a database.sqlite file in the database directory. This is the default SQLite database as configured in config/database.php
  4. Run php artisan migrate on the route of your Laravel project. This will set up a users table in the database.

We will then add a route and a controller to get the first user in our users table who just so happens not to exist.

app/Http/routes.php

Route::get('/user', [
    'uses' => 'SampleController@findUser',
    'as' => 'user'
]);

App/Http/Controllers/SampleController.php

     /**
     * Return the first user in the users table
     *
     * @return Array    User details
     */
    public function findUser()
    {
        $user = User::firstOrFail();
        return $user->toArray();
    }

Running this on the browser will return a ModelNotFoundException error response.

NotFoundHttpException

Catching Exceptions with a Custom Handler

ModelNotFoundException

With this exception, we can now add a custom handler that returns our own error message.

We will modify the render method in app/Exceptions/Handler.php to return a JSON response for an ajax request or a view for a normal request if the exception is one of ModelNotFoundException or NotFoundHttpException.

If it is neither of the two, we will let Laravel handle the exception.

   /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
        //check if exception is an instance of ModelNotFoundException.
        if ($e instanceof ModelNotFoundException) {
            // ajax 404 json feedback
            if ($request->ajax()) {
                return response()->json(['error' => 'Not Found'], 404);
            }

            // normal 404 view page feedback
            return response()->view('errors.missing', [], 404);
        }

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

Add a 404.blade.php file in resources/view/errors to contain our user feedback.

<!DOCTYPE html>
<html>
<head>
    <title>User not found.</title>
</head>
<body>
    <p>You broke the balance of the internet</p>
</body>
</html>

If we now refresh the page, we have the following message on our view with a 404 status code.

Custom exception handling with 404 status code feedback

NotFoundHttpException

When a user visits an undefined route such as /foo/bar/randomstr1ng, a NotFoundHttpException exception, which comes as part of the Symfony package, is thrown.

To handle this exception, we will add a second condition in the render method we modified earlier and return a message from resources/view/errors/missing.blade.php

/**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
        //check if exception is an instance of ModelNotFoundException.
        //or NotFoundHttpException
        if ($e instanceof ModelNotFoundException or $e instanceof NotFoundHttpException) {
            // ajax 404 json feedback
            if ($request->ajax()) {
                return response()->json(['error' => 'Not Found'], 404);
            }

            // normal 404 view page feedback
            return response()->view('errors.missing', [], 404);
        }

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

Taking Advantage of Laravel’s Abort Method

Just like we did in the previous section, Laravel 5.2 makes it all too easy to create custom error pages based on the exception that was thrown.

We can also simply generate a 404 error page response by calling the abort method which takes an optional response message.

abort(404, 'The resource you are looking for could not be found');

This will check for a corresponding resources/view/errors/404.blade.php and serve an HTTP response with the 404 status code back to the browser. The same applies to 401 and 500 error status codes.

Conclusion

Depending on an application’s environment, you may want to show varying levels of error details. You can set the APP_DEBUG value in config/app.php to either true or false by changing it in your .env file.

In most cases, you may not want your users in production to see detailed error messages. It is therefore good practice to set APP_DEBUG value to false while in a production environment.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
John Kariuki

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.