Laravel's exceptions: Part 3 – findOrFail exception automated
Tags: php, laravel, programming, tutorial
Published: 2022-02-18 12:33:40

When implementing something that requires querying the database to find a record by its ID, the first thing that comes to mind is the `find()` function from [Eloquent](https://laravel.com/docs/9.x/eloquent).

Even though the function works, and you will be able to retrieve your data when it exists, a null object will be returned if the record is not found, and this null object usually brings unnecessary complexity.

As you know, Laravel is a full-featured framework, but one of the features that blew my mind once I got the hang of it, was the exception handling of the framework. It’s not random that some of Laravel’s helpers throw exceptions instead of returning null objects, such as the `findOrFail()` function.

These helpers that throw exceptions can be pretty handy when you learn how to automate the handling of their exceptions, making the code cleaner and shorter. But before we dive into those functions or Laravel’s exception handler, let’s take a look at ways that a find operation can be implemented.

### Content
- [Ways to implement](#ways-to-implement)
    - [The find function](#with-the-find-function)
    - [The findOrFail function](#with-the-findorfail-function)
- [Automating exceptions](#automating-exceptions)
- [Final implementation](#final-implementation)

## Ways to implement

In the following two sections, we will see the most conventional ways of implementing a find operation. You will notice that the number of lines between the implementations is the same; although they are entirely different implementations and there are no real advantages between them, the choice of the approach is just a matter of preference.

### The find function

Down below, we have a real use case where we are implementing a show action to the UsersController to find a user by its ID and render the record as a response to an endpoint.

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class UsersController extends Controller
{
    public function show(Request $request): Response
    {
        $user = User::find($request->id);

        if ($user === null) {
            return response("User with id {$request->id} not found", Response::HTTP_NOT_FOUND);
        }

        return response($user);
    }
}
```

First, we query a user by its ID using the find function and store its result in an `$user` object.

Next, because the record of a given ID may not be found in the database, we are checking if the `$user` is null; in case it is, we will return a message that would evaluate to `User with id 1 not found`.

Finally, if the ID passed into the query got found, the user object would be present, and we could just return it as a response to the API call.

### The findOrFail function

With the same use case as before, let’s try a second approach and see how our code looks when using the `findOrFail()` function without automating its exception handling.

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Database\Eloquent\ModelNotFoundException;

class UsersController extends Controller
{
    public function show(Request $request): Response
    {
        try {
            $user = User::findOrFail($request->id);
        } catch (ModelNotFoundException $exception) {
            return response("User with id {$request->id} not found", Response::HTTP_NOT_FOUND);
        }

        return response($user);
    }
}
```

At the `try{}` block we are querying the user by its ID; when found, we will place the data at the `$user`, but unlike the previous approach, a ModelNotFoundException will be thrown in case the record doesn't exist.

Thereafter, the `catch{}` block gets used to intercept the exception thrown; once the exception gets intercepted, we build a response with a message that would be evaluated to `User with id 1 not found`.

Then, if the given ID gets found, the `$user` object would be present, and we would return it as a response to the endpoint.


### Automating exceptions

It’s important to notice that the approach about to be explained can be used to automate the handling of various Laravel exceptions. By automating these exceptions, you will have cleaner code and a more structured way of handling the exceptions.

To automatically handle the exception thrown by the `findOrFail()` function, we will have to override the `render()` function in the **app/Exceptions/Handler.php** class, as shown in the example down below.

```php
<?php

namespace App\Exceptions;

use Throwable;
use Illuminate\Support\Arr;
use Illuminate\Http\Response;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $e
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Throwable
     */
    public function render($request, Throwable $e)
    {
        if ($e instanceof ModelNotFoundException) {
            $replacement = [
                'id' => collect($e->getIds())->first(),
                'model' => Arr::last(explode('\\', $e->getModel())),
            ];

            $error = new Error(
                help: trans('exception.model_not_found.help'),
                error: trans('exception.model_not_found.error', $replacement)
            );

            return response($error->toArray(), Response::HTTP_NOT_FOUND);
        }

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

We start by declaring a conditional that checks if the `$e` being received in the `render()` parameter is an instance of the ModelNotFoundException thrown by the `findOrFail()` used in the previous examples.

Then, we create a `$replacement` array, an associative with the keys id and model, whose values are getting extracted from the intercepted exception. This replacement array gets used as a [replacement parameter](https://laravel.com/docs/8.x/localization#replacing-parameters-in-translation-strings) in a `trans()` function that gets called in the constructor of the error object.

Subsequently, we are creating an `$error` object. In case you don’t know what this _Error_ class is about, please take a moment to read the previous post in this series, where I discuss [custom exceptions](https://dev.to/jackmiras/laravels-exceptions-part-2-custom-exceptions-1367). The error class takes two mandatory parameters: `$help` which shows a possible solution to the error, and `$error` where the error that happened gets specified.

Note that we are using named parameters and the `trans()` helper to specify the messages we want to pass into the `$help` and `$error` parameters from the error object.

Lastly, with the error object instantiated, we can build the response object to be rendered to the API call. To achieve that, we are using the `response()` function passing `$error->toArray()` as the first parameter, and `Response::HTTP_BAD_REQUEST` as the second one, which is the constant of the HTTP status 400.

### Final implementation

As a final result, we have a show action that can be implemented with one single line, without the need for checking for a null object nor  try/catch blocks to handle the exception getting thrown.

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class UsersController extends Controller
{
    public function shows(Request $request): Response
    {
        return response(User::findOrFail($request->id));
    }
}
```

---

Now our findOrFail exception automation is done.

Happy coding!