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...