DEV Community

Cover image for Handling Exceptions in Laravel: A Cleaner Method
Timilehin Olusegun
Timilehin Olusegun

Posted on • Updated on

Handling Exceptions in Laravel: A Cleaner Method

When building web applications, you will likely use various APIs and services to add functionality to your software. While using these external services, you might run into unexpected errors that cause the application to crash. To avoid these unexpected crashes and ensure the smooth running of your application, you need to handle them properly.

The conventional way of handling exceptions in the PHP programming language is by using try-catch blocks and the same method applies to Laravel. In this article, I will show you a shorter and cleaner way to handle exceptions in Laravel.

In this article, I will be showing you a shorter and cleaner way to handle exceptions in Laravel.

Prerequisites

Before you begin this tutorial you'll need the following:

  • Web Browser
  • Your preferred IDE
  • Basic knowledge of PHP and the Laravel framework

What are Exceptions?

Exceptions are occurrences that emerge while a program is being executed and causes a disruption in the execution flow of the program. When an exception occurs, the program crashes and the user cannot continue using the program. The program will have to be restarted for it to be used again. In order to avoid these occurrences, we have to handle them.

Handling Exceptions in Laravel

Now we have a simple form with an input field which allows us to search for a user by email and then display their details.

Simple form

So we have two routes.



Route::get('/', [UserController::class, 'index']);
Route::post('/search', [UserController::class, 'search'])->name('search');


Enter fullscreen mode Exit fullscreen mode

We also have your Blade template file, resources/views/index.blade.php where we have the code for the form.



<form action="{{ route('search') }}" method="post">
    @csrf
    <div class="mb-4 mt-4">
        <div class="form-floating mb-3">
            <input type="email" name="email" id="floatingEmail" class="form-control @error('email') is-invalid @enderror" value="{{ old('email') }}" placeholder="Enter your email">
            <label for="floatingEmail">Email address</label>
        </div>
        @error('email')
            <span class="text-danger mb-1">{{ $message }}</span>
        @enderror
    </div>
    <div class="d-grid col-6 col-md-3 mx-auto">
        <button class="btn btn-secondary" type="submit" id="store">Search</button>
    </div>
</form>


Enter fullscreen mode Exit fullscreen mode

In the controller, we have two methods; the index method and the search method. The index method shows the index page. The search method validates the input email and then uses it to search for the user’s details in the database.



class UserController extends Controller
{
    public function index()
    {
        return view('index');
    }


    public function search(Request $request)
    {
        $validated = $this->validate($request, [
            'email' => 'required|email',
        ]);
        $email = $validated["email"];

        $user = User::where('email', $email)->first();
        return view('result', compact('user'));

    }

}


Enter fullscreen mode Exit fullscreen mode

When we search for a user by email and the user is found, we get the user’s details as shown below.

Expected Result

Now, what happens when a user is not found?

Exception Handling using the Try-Catch block

If the user is not found, we get an error exception page.

Image description
This error page needs to help the user understand what is going on. We could use firstOrFail() instead of first(). This will make Laravel display a “404 | Not Found” page.

Image description

But this page is also not really helpful to the user. What we need to do is to catch the exception, and then display a message that is understandable.

In Laravel, firstOrFail() throws an Eloquent exception, ModelNotFoundException. With this knowledge, we can handle the exception using the try-catch block in the search method of the User Controller.



public function search(Request $request)
{
    $validated = $this->validate($request, [
        'email' => 'required|email',
    ]);

    $email = $validated["email"];

    try {
        $user = User::where('email', $email)->firstOrFail();
    } catch (ModelNotFoundException $exception) {
        return back()->with('error',$exception->getMessage())->withInput();
    }
}


Enter fullscreen mode Exit fullscreen mode

Now we can show an error on the homepage. So we go to our Blade template file, resources/views/index.blade.php to display the error message.



@if (session('error'))
<div class="row justify-content-center">
    <div class="alert alert-danger alert-dismissible fade show col-md-6" role="alert">
        <strong>Message:</strong> {{ session('error') }}
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </div>
</div>
@endif


Enter fullscreen mode Exit fullscreen mode

This is the result of displaying the error message.

Image description

Now imagine we have to handle just more than one exception. We have to catch each exception one by one. Also when using external services in your application, these external services could have multiple custom exception classes depending on the error encountered. A good example is the Stripe library for Stripe Payments API.



try {
  // Use Stripe's library to make requests...
} catch(\Stripe\Exception\CardException $e) {
  // Since it's a decline, \Stripe\Exception\CardException will be caught
  echo 'Status is:' . $e->getHttpStatus() . '\n';
  echo 'Type is:' . $e->getError()->type . '\n';
  echo 'Code is:' . $e->getError()->code . '\n';
  // param is '' in this case
  echo 'Param is:' . $e->getError()->param . '\n';
  echo 'Message is:' . $e->getError()->message . '\n';
} catch (\Stripe\Exception\RateLimitException $e) {
  // Too many requests made to the API too quickly
} catch (\Stripe\Exception\InvalidRequestException $e) {
  // Invalid parameters were supplied to Stripe's API
} catch (\Stripe\Exception\AuthenticationException $e) {
  // Authentication with Stripe's API failed
  // (maybe you changed API keys recently)
} catch (\Stripe\Exception\ApiConnectionException $e) {
  // Network communication with Stripe failed
} catch (\Stripe\Exception\ApiErrorException $e) {
  // Display a very generic error to the user, and maybe send
  // yourself an email
} catch (Exception $e) {
  // Something else happened, completely unrelated to Stripe
}


Enter fullscreen mode Exit fullscreen mode

As we can see, you have to catch every single error so as to handle them properly to suite your application’s needs. This makes the lines of code we have to write increase and this can make our code harder to maintain. Though this method is perfectly fine, there is an even shorter and slicker way to handle exceptions with Laravel.

Handling Exceptions Using the Rescue Helper

Laravel provides us with the rescue helper which executes your code or task as a closure. The rescue helper catches any exceptions that occur during the execution of the closure. Any exception caught is sent to the exception handler in your Laravel application.



public function search(Request $request)
{
    $validated = $this->validate($request, [
        'email' => 'required|email',
    ]);

    $email = $validated["email"];

    return rescue(function () use ($email) {
        $user = User::where('email', $email)->firstOrFail();
        return view('result', compact('user'));
    }, function ($exception) {
        return back()->with('status', $exception->getMessage());
    }, true);
}


Enter fullscreen mode Exit fullscreen mode

Generally, the rescue method can accept three arguments.

  • The first argument is a closure that contains the piece of code you want to execute. In our case, we want to search for the user by email and display their details on another page.
  • The second argument (optional) is the closure executed if an exception occurs in the previous argument. You can also pass in an argument representing the exception thrown.
  • The third argument (optional) accepts a boolean value. This argument tells the function whether or not to report the exception to the configured exception handler. If true, the rescue() helper reports the exception and vice-versa.

Conclusion

In cases where you might have multiple exceptions to handle, the rescue() helper can help you reduce the number of lines of code you have to write. It can also help improve the readability of your code thereby making it easier to maintain over time.

I hope this article has shown you how to handle exceptions more shortly and cleanly. You can get the example code discussed on GitHub. Cheers!

Further Reading

Top comments (2)

Collapse
 
lezhni profile image
Nik Lezhnevich • Edited

That's equals to
try {
...
} catch (\Throwable $exception) {
...
}

isn't it?

Collapse
 
timilehin-olusegun profile image
Timilehin Olusegun

Hi there!

Well yes, only that you don't have to specify each exception when you have multiple exceptions to handle.