Advanced Error Handling in Laravel
Tags: php, laravel
Published: 2023-07-20 17:24:35

*This article was originally written by [Devin Gray](https://www.honeybadger.io/blog/authors/devingray/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/advanced-error-handling-in-laravel/).*

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.

```bash
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:

```php
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](https://www.honeybadger.io/images/blog/posts/advanced-error-handling-in-laravel/default_route.png)

## 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](https://www.honeybadger.io/images/blog/posts/advanced-error-handling-in-laravel/default_404.png)

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.

```html
<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](https://www.honeybadger.io/images/blog/posts/advanced-error-handling-in-laravel/not_found.gif)

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.

```php
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](https://www.honeybadger.io/images/blog/posts/advanced-error-handling-in-laravel/runtime_exception.png)

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](https://www.honeybadger.io/images/blog/posts/advanced-error-handling-in-laravel/default_500.png)

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:

```php
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:

```php
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:

```bash
[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](https://docs.honeybadger.io/lib/php/integration/laravel.html) 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:

```php
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:

```bash
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:

```bash
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
<?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.

```php
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](https://www.honeybadger.io/images/blog/posts/advanced-error-handling-in-laravel/custom_exception.png)

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.