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.
So we have two routes.
Route::get('/', [UserController::class, 'index']);
Route::post('/search', [UserController::class, 'search'])->name('search');
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>
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'));
}
}
When we search for a user by email and the user is found, we get the user’s details as shown below.
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.
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.
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();
}
}
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
This is the result of displaying the error message.
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
}
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);
}
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!
Top comments (2)
That's equals to
try {
...
} catch (\Throwable $exception) {
...
}
isn't it?
Hi there!
Well yes, only that you don't have to specify each exception when you have multiple exceptions to handle.