Laravel's exceptions: Part 2 – Custom exceptions
Tags: php, laravel, programming, tutorial
Published: 2022-02-10 02:00:33

After working with Laravel for a while and learning about its way of handling exceptions, I found myself creating my own exceptions, either to simplify some if/else statements or to terminate function calls or even features.

These custom exceptions are helpful, especially when combined with the [throw_if](https://laravel.com/docs/8.x/helpers#method-throw-if) and [throw_unless](https://laravel.com/docs/8.x/helpers#method-throw-unless) functions, which provide a cleaner way of throwing conditional exceptions.

### Content
- [Understanding custom exception](#understanding-custom-exceptions)
- [Structuring custom exceptions](#structuring-custom-exceptions)
    - [Making exceptions simple and extensible](#making-exceptions-simple-and-extensible)
    - [Standardizing errors](#standardizing-errors)
- [Creating the JsonEncodeException](#creating-the-jsonencodeexception)

### Understanding custom exceptions


Laravel can handle custom exceptions automatically when the exception is created in a certain way. First, the exception has to extend the Exception class, and then, in case you want to render something to the end user, you have to override the `render()` function.

If you need to create an exception that will extend a class that isn’t the Exception class, Laravel won’t be able to automatically handle the exception, and as a result, you will have to use a try/catch block.

Check the following code example to better understand how a custom exception can be defined.

```php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Response;

abstract class MyCustomException extends Exception
{

    public function render(Request $request): Response
    {
        $status = 400;
        $error = "Something is wrong";
        $help = "Contact the sales team to verify";

        return response(["error" => $error, "help" => $help], $status);
    }
}

```

The result, from the exception above, would be a response with HTTP status 400 and a JSON in the following format:

```json
{
    "error": "Something is wrong",
    "help": "Contact the sales team to verify"
}
```

To test this example and validate the rendered JSON, you can use plain PHP to throw the exception just like this `throw new MyCustomException()`;

### Structuring custom exceptions

Even though the creation of custom exceptions is pretty straightforward, it’s possible to design a more robust architecture around exceptions. The design that will be presented in this post has two premises.

 1) Making exceptions simple and extensible.
 2) Standardize the way error responses are shown.

For them to be simpler and more extensible, we need to define a structure from which every custom exception can derive.

As for standardizing them, it’s required to define how the error will be shown to the end user, and it is important to display your errors in a single, structured manner. Otherwise, front-end applications or other APIs integrating with yours won’t be able to trust your error responses, and these kinds of applications are just horrible to work with.

#### Making exceptions simple and extensible

So, how can we implement custom exceptions in a way that we can enforce that every exception will comply with the appropriate status code, error, message, and other properties consistently on a basis that can be reused? For that, we can define an abstraction that all custom exceptions will implement, as the code below shows.

```php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

abstract class ApplicationException extends Exception
{
    abstract public function status(): int;

    abstract public function help(): string;

    abstract public function error(): string;

    public function render(Request $request): Response
    {
        $error = new Error($this->help(), $this->error());
        return response($error->toArray(), $this->status());
    }
}
```

Here, an abstract class gets defined to enforce that every exception will implement a `status()`, `help()` and `error()` functions.

In the `render()` function that we are overriding from the `Exception` class, we are using the result of the implementation of our help and error methods to create an error object.

The error object is responsible for standardizing the errors in the application; it doesn’t matter whether the error was from an exception or a business rule validation; if it is an error, it will be encapsulated into the error object.

#### Standardizing errors

To standardize errors, we will create the already mentioned and previously used error object. This object has two main characteristics.

1) Define the data properties that will be shown when rendering the error.
2) Be able to be automatically transformed into JSON by Laravel.

```php
<?php

namespace App\Exceptions;

use JsonSerializable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Arrayable;

class Error implements Arrayable, Jsonable, JsonSerializable
{
    public function __construct(private string $help = '', private string $error = '')
    {
    }

    public function toArray(): array
    {
        return [
            'error' => $this->error,
            'help' => $this->help,
        ];
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }

    public function toJson($options = 0.0)
    {
        $jsonEncoded = json_encode($this->jsonSerialize(), $options);
        throw_unless($jsonEncoded, JsonEncodeException::class);
        return $jsonEncoded;
    }
}
```

Notice that the class declaration implements three interfaces; these interfaces are responsible for making the class capable of being converted into an array or a JSON.

Following the class declaration, we have the constructor where property promotion is getting used to keep the code cleaner; as parameters, we have `$help` which shows a possible solution to the error getting shown, and `$error` where the error that happened gets specified.

Next, we have the `toArray()` function, where the error object gets returned as an array.

Then we have the `jsonSerialize()` function, where the error object also returns as an array. Here we are applying DRY and just calling the `toArray()` inside the `jsonSerialize()` function; since they return the same result, there is no reason for us to repeat ourselves.

Finally, we have the `toJson()` function, where our object gets converted into a JSON string. Notice that we start by calling the `json_encode()`. To this function, we are passing as parameters the result from `jsonSerializer()` previously implemented and the `$options` from the function itself.

As a result, we should have the `$jsonEncoded` variable a JSON string matching the information present in our error object; however, in cases where `json_encode()` can’t transform the array passed as a parameter into JSON, it will return a boolean false.

For that reason, in the next line we’re using the `throw_unless()` helper to throw an exception in case `$jsonEncoded` is evaluated to false, and otherwise we return the JSON string in the subsequent line.
Now we just need to define the `JsonEncodeException` that we conditionally throw in the `toJson()` function.

### Creating the JsonEncodeException

Considering that this exception is a custom exception written specially to handle the JSON conversion of our error object, it makes perfect sense to use our `ApplicationException` from the previous session.

```php
<?php

namespace App\Exceptions;

use Illuminate\Http\Response;

class JsonEncodeException extends ApplicationException
{
    public function status(): int
    {
        return Response::HTTP_BAD_REQUEST;
    }

    public function help(): string
    {
        return trans('exception.json_not_encoded.help');
    }

    public function error(): string
    {
        return trans('exception.json_not_encoded.error');
    }
}
```


Notice that once we extend the `ApplicationException` class, we have the obligation to implement the status, help, and error functions. Inside these functions, we implemented the return of the values that make sense in the context of this exception.

In this specific example, we are returning HTTP status 400, and we are getting translated texts from Laravel’s translating scheme.

Keeping all your messages and texts in Laravel’s translation structure is always a good decision. Having to translate an app after a lot of written code is often more difficult and requires much more refactoring than starting the app with this pattern.

___

Now our custom exception structure and encapsulation of errors are done.

Happy coding!