Advanced Error Handling in Laravel

This article explains exceptions and how to handle them gracefully in Laravel. Learn how to create custom exceptions, work with exception handlers, integrate with 3rd-party error-tracking services, and create robust, scalable web applications!

Laravel is one of the fastest growing frameworks in the open-source space. More developers are starting to see the value that Laravel can bring to any project. With "out of the box" routing, queuing, testing, and so much more, Laravel provides a perfect starting point for your next big idea. Included with any default Laravel project is a base starting point for all exceptions and errors to be handled in a graceful way. However, as this is only a starting point, you can expand your error handling capabilities to match any requirements that arise.

In this article, we will explore errors and exceptions within a Laravel application and discuss ways to make errors more robust and scalable.

Understanding Exceptions

To fully understand error and exception handling, let's walk through a basic Laravel project to find out exactly what is meant by errors and exceptions. To begin, let's set up a new Laravel project; fire up a terminal and run the following command:

Note: I have named my project "handled"; you are free to choose any name you like.

composer create-project laravel/laravel handled

This will spin up a brand-new Laravel application for us to play with. First, we should find out what types of errors we have and how they work with the default behavior, so let's break some things!

Head over to routes/web.php and look at the default route:

Route::get('/', function () {
    return view('welcome');
});

Without going into too much detail about what the above code does, visiting the app in the browser will render the default Laravel home page that can be found at resources/views/welcome.blade.php and looks like this:

Note: I am using Laravel Valet to run this in the browser.

Default Laravel Welcome Screen

The Lost and Found

To begin our journey to breaking the application, visiting http://handled.test in the browser will render a view and display the content of that view, but what if we visited http://handled.test/nothing-here? We expect to have a 404 page, so how would Laravel handle this?

Default Laravel 404 Screen

As you can see, it is handled for us. We did not need to build this 404 page; Laravel has included it by default. However, you would not want to keep this in a production application because cat gifs are way cooler, so let's try to replace this with our own. The quick and easy way is to create a file at resources/views/errors/404.blade.php and replace or add some HTML for this file.

<div>
    <h1>No Dice</h1>
    <img src="https://thecatapi.com/api/images/get?format=src&type=gif" alt="404">
</div>

This should do the trick! If we refresh the browser, we should see the following:

Custom 404 Screen

Thus, we have overwritten Laravel's custom 404 Page.

Stronger Exceptions

Although 404 exceptions are unavoidable in any application, luckily, they are easy to handle. However, what about a real exception? What happens when PHP throws a runtime exception? Let's find out. Head back to your routes/web.php file, and we’ll really break something.

Route::get('/', function () {
    return view('welcome', [
        'data' => $variableMissing
    ]);
});

The above code will cause a PHP runtime exception, as the $variableMissing is undefined. Unlike JavaScript, it will continue to run if this happens. PHP will automatically quit on that line and throw an exception. No further code will be run after the error, and it will look like this:

Run Time Exception

This is a bit more complicated than the previous 404 error, which was simply a page, and there is a good reason for this. The page you can see above should not be available to production applications, but in a local environment, it will help to debug and fix any issues. To see what it would look like in production, we can change one of our env values.

APP_DEBUG=false

Give it a refresh, and it will look like this:

Default 500 Screen

We’ll follow the steps we took to create a custom 404 page, but we’ll create a file named resources/views/errors/500.blade.php to capture all 500 errors and display the page.

Digging Deeper

Now that we have a better understanding of what errors are and how we can handle them with custom views, we’ll take a deeper look into how these errors are displayed and what we can do to hook into them to add additional logic or requirements. To get started, let's ensure our .env file is back to the local settings to allow debug options:

APP_DEBUG=true

Refreshing the page will once again show the Ignition error page with context about the error. As we can see this displays and lets us know that we have the following issue in our routes file:

Undefined variable $variableMissing

However, what if we wanted to perform some logging on these errors? For example, what if I wanted to send this error to error-tracking software, such as Honeybadger? We would need a way to hook into it before it rendered this page.

The Error Handler

In Laravel, all exceptions pass through a file that can be found here: app/Exceptions/Handler.php. In this file, we can interact with an exception before it is thrown, which allows us to perform additional logic for each error we receive.

Given the above example with the undefined variable, let's go into the app/Exceptions/Handler.php and simply hook into this exception to do some logic.

By default, the register method on this file is blank, as shown below:

public function register()
{
    $this->reportable(function (Throwable $e) {
       // 
    });
}

This does nothing and simply runs through the exception, but this handler class allows us to register additional reporting and rendering callbacks that can be used to log exceptions to third-party tools such, as Honeybadger. For the sake of simplicity, let's simply attempt to log our exception to our filesystem as the first step.

To do this, we will add some custom code to the reportable callback:

public function register()
{
    $this->reportable(function (Throwable $e) {
       Log::info($e->getMessage());
    });
}

Refreshing the page will show the same error as before, but there will be a new log created in storage/logs/laravel.log that will contain the following message:

[2022-08-03 08:07:56] local.INFO: Undefined variable $variableMissing

This is not typically something one would do, as this log file is not easily accessible on a production server. However, the point is still illustrated, and it is that simple to include additional logic.

Let's try to do something slightly more complicated in the next steps.

Useful Error Logging with Honeybadger

To make this reportable method more useful, let's tackle a real-world situation. For this, we’ll send our error to error tracking software, Honeybadger.io, which is easy to set up and hooks directly into the above code that we’ve already written.

Installing Honeybadger is simple and easy, so please head over to the Honeybadger Laravel Docs and follow the step-by-step guide to install it. You will notice that all you need to do is install the composer dependency and set up the reportable callback:

public function register()
{
    $this->reportable(function (Throwable $e) {
        if (app()->bound('honeybadger')) {
            app('honeybadger')->notify($e, app('request'));
        }
    });
}

After installing the Honeybadger composer dependency, run the following:

php artisan honeybadger:install your-project-api-key

Laravel will now send all error data to Honeybadger instead of a log file, which is much easier and cleaner to work with.

Creating Custom Exceptions

To Recap, we have shown what is considered an error in Laravel, how to edit the default pages, and how to hook into the exceptions that are thrown. However, let's take this tutorial a step further and build a custom exception.

Custom exceptions can be used for a number of purposes. The one I find most useful is to provide context for which area of the application I need to look into. For this simple use case and demo purposes, we’ll name our exception FailedToLoadHomePage and fire this if we are unable to render our home-page view for whatever reason.

Fire up that terminal and let's get started:

php artisan make:exception FailedToLoadHomePage

This will create a new class for us here app/Exceptions/FailedToLoadHomePage.php, and this class contains the following boilerplate:

<?php

namespace App\Exceptions;

use Exception;

class FailedToLoadHomePage extends Exception
{
    //
}

Now what we want to do is return to our route file and throw this exception when something goes wrong.

Route::get('/', function () {
    try {
        return view('welcome', [
            'data' => $variableMissing
        ]);
    } catch (Throwable $exception) {
        throw new FailedToLoadHomePage($exception->getMessage());
    }
});

After giving the browser a refresh, we can see that our new exception has been thrown instead of the default one.

Custom Exception

This is pretty neat! We have customized Laravel exceptions to work as needed.

When to use a custom exception?

While the whole process is pretty easy to build and configure, it is generally not required to make a custom exception for every single thing that may break in your app. The best times to use a custom exception is when you need additional behavior for a given exception. A good example of this would be to report to slack when something specific happens.

For example, imagine you are building an app that connects to an API. If the API is returning errors, it may be important to write a custom exception to provide additional context for what has gone wrong and why. This can save development and debugging time by understanding what is wrong before trying to fix it.

Conclusion

Extending and manipulating the exception handlers in Laravel is extremely easy. Laravel provides all the fundamentals needed to get up and running with defaults, and you are free to make it your own.

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Devin Gray

    Laravel Enthusiast… Part Time Human Being

    More articles by Devin Gray
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial