Black Friday: coupon FRIDAY24 for 40% off Yearly/Lifetime membership! Read more here
Courses

Handling Exceptions and Errors in Laravel

Why Try-Catch? Exception Examples

Exceptions can happen almost anywhere on your application, which will produce an error like this:

And this is the screen we see locally, but in Production, we would display a more generic message to the user:

This error does not give any information to the user and provides a poor user experience.


How to Handle Exceptions

Handling Exceptions is a simple process. We wrap the Code that might throw an Exception in a Try-Catch block and handle the Exception in the Catch block. Let's look at an example:

try {
//Code that may throw an Exception
} catch (Exception $e) {
// Log the message locally OR use a tool like Bugsnag/Flare to log the error
Log::debug($e->getMessage());
 
// Either form a friendlier message to display to the user OR redirect them to a failure page
}

But the question arises - what can we do besides custom logging? Well, let's look at how the world uses it:


Examples in Open Source E-Commerce Platforms

We'll take a look at Bagisto, a Laravel-based Open Source E-Commerce Platform, and see what they do with their Exceptions:

In this example, we can see that they are surrounding the whole method of adding a product to the cart with a Try-Catch block. If an Exception is thrown, they are doing a few actions:

  • Generating a Flash message to the user. This allows them to display a message in the UI.
  • Logging the error. Logs the error for the developer to see.
  • Generates a new response. They are generating a new response which indicates that an issue happened.

packages/Webkul/Velocity/src/Http/Controllers/Shop/CartController.php

// ...
 
public function addProductToCart()
{
try {
$cart = Cart::getCart();
 
$id = request()->get('product_id');
 
if ($product = $this->productRepository->findOrFail($id)) {
if (! $product->visible_individually) {
abort(404);
}
}
 
$cart = Cart::addProduct($id, request()->all());
 
// ...
} catch(\Exception $exception) {
session()->flash('warning', __($exception->getMessage()));
 
$product = $this->productRepository->find($id);
 
Log::error('Velocity CartController: ' . $exception->getMessage(),
['product_id' => $id, 'cart_id' => cart()->getCart() ?? 0]);
 
$response = [
'status' => 'danger',
'message' => __($exception->getMessage()),
'redirectionRoute' => route('shop.productOrCategory.index', $product->url_key),
];
}
 
return $response ?? [
'status' => 'danger',
'message' => __('velocity::app.error.something_went_wrong'),
];
}
 
// ...

Another example can be seen in their Menu generation, where they handle the Exception by setting a variable as a fallback:

packages/Webkul/Velocity/src/Resources/views/shop/customers/account/partials/sidemenu.blade.php

{{-- ... --}}
 
@php
$subMenuCollection = [];
 
try {
$subMenuCollection['profile'] = $menuItem['children']['profile'];
$subMenuCollection['orders'] = $menuItem['children']['orders'];
$subMenuCollection['downloadables'] = $menuItem['children']['downloadables'];
 
if ((bool) core()->getConfigData('general.content.shop.wishlist_option')) {
$subMenuCollection['wishlist'] = $menuItem['children']['wishlist'];
}
 
if ((bool) core()->getConfigData('general.content.shop.compare_option')) {
$subMenuCollection['compare'] = $menuItem['children']['compare'];
}
 
$subMenuCollection['reviews'] = $menuItem['children']['reviews'];
$subMenuCollection['address'] = $menuItem['children']['address'];
 
unset(
$menuItem['children']['profile'],
$menuItem['children']['orders'],
$menuItem['children']['downloadables'],
$menuItem['children']['wishlist'],
$menuItem['children']['compare'],
$menuItem['children']['reviews'],
$menuItem['children']['address']
);
 
foreach ($menuItem['children'] as $key => $remainingChildren) {
$subMenuCollection[$key] = $remainingChildren;
}
} catch (\Exception $exception) {
$subMenuCollection = $menuItem['children'];
}
@endphp
 
{{-- ... --}}

Examples in Twill CMS

Another set of examples can be found in Twill CMS, where they are handling the exceptions similarly:

An example can be taken from Dashboard, where they are attempting to get an author, but if that fails - they use a default value of Admin:

src/Http/Controllers/Admin/DashboardController.php

// ...
 
public function search(Request $request): Collection
{
// ...
return $modules->filter(function ($module) {
return $module['search'] ?? false;
})->map(function ($module) use ($request) {
return $found->map(function ($item) use ($module) {
try {
$author = $item->revisions()->latest()->first()->user->name ?? 'Admin';
} catch (\Exception) {
$author = 'Admin';
}
 
// ...
});
})->collapse()->values();
}
 
// ...

Next, we can see that they attempt to get the latest revision of an asset, but if that fails, they return a default value so that the application would work:

src/Helpers/frontend_helpers.php

// ...
 
function revAsset($file)
{
if (!app()->environment('local', 'development')) {
try {
$manifest = Cache::rememberForever('rev-manifest', function () {
return json_decode(file_get_contents(config('twill.frontend.rev_manifest_path')), true);
});
 
if (isset($manifest[$file])) {
return (rtrim(config('twill.frontend.dist_assets_path'), '/') . '/') . $manifest[$file];
}
} catch (Exception $exception) {
return '/' . $file;
}
}
 
return (rtrim(config('twill.frontend.dev_assets_path'), '/') . '/') . $file;
}
 
// ...

You can Throw Exceptions too!

While Exceptions are handled with Try-Catch blocks, you can also throw new Exceptions. In our case, we want to throw a new ValidationException if the image upload fails:

use Illuminate\Validation\ValidationException;
 
// ...
 
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
 
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
 
$request->user()->save();
 
if ($request->hasFile('avatar')) {
try {
$request->user()->addMediaFromRequest('avatar')->toMediaCollection();
} catch (Exception $e) {
Log::debug($e->getMessage());
 
throw ValidationException::withMessages([
'avatar' => __('Selected avatar image failed to upload. Try again or select a different image.'),
]);
}
}
 
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}

By doing this, we have stopped the execution of the method, and with the help of Laravel's ValidationException - we can display the error message to the user and return him to the form.

No comments or questions yet...