Adam Crampton
Posted on September 9, 2019
The Problem
I recently found myself in a situation where I had a codebase that would throw exceptions from certain Eloquent queries - but in a sort of legitimate way.
Essentially, the scenario was that certain user accounts did not have relationships with certain models, but were critical for the controller to complete its work.
The Solution
This sort of thing should normally be dealt with by fixing the issue at the entry point (validation on user creation), however in this particular scenario, it was a much better idea to return an error to the user to let them know that human intervention was required.
So, to solve this, I needed to catch the exception, generate a human-readable message, and send it back to the front end. This functionality should not clutter the controllers, and should be easy to maintain.
Here's my step-by-step approach:
1. Set up a trait
Since we probably want this functionality to be available globally through the app, let's set up a trait - which are super useful for this kind of thing.
namespace App\Traits;
Trait GlobalUtility
{
// Code
}
2. Define the errors
Within the trait, we set up an array of error messages, with a meaningful key name for each. A default message is added to ensure something is always returned.
namespace App\Traits;
Trait GlobalUtility
{
protected $customExceptionErrors = [
'generic' => 'Sorry, there was a problem with this page. Please contact your Sales Account Manager for help.',
'noDealerRelationship' => 'Sorry, your user account does not appear to be connected to a Dealer. Please contact your Sales Account Manager for help.'
];
}
3. Return logic
The return method is a simple check that:
- Accepts a named exception parameter, which is aligned with the keys in the
$customExceptionErrors
array - Fetches the error message
- Returns the error string or the generic version if none is found (or no parameter is passed)
namespace App\Traits;
Trait GlobalUtility
{
protected $customExceptionErrors = [
'generic' => 'Sorry, there was a problem with this page. Please contact your Sales Account Manager for help',
'noDealerRelationship' => 'Sorry, your user account does not appear to be connected to a Dealer. Please contact your Sales Account Manager for help.'
];
/**
* Returns a custom error message for the exception.
*
* @param string $exceptionName
* @return string
*/
public function getExceptionError($exceptionName = 'generic')
{
return $this->customExceptionErrors[$exceptionName] ?: $this->customExceptionErrors['generic'];
}
}
4. Add Trait and Exception Class to Controller
Next, the trait and exception class are added to the controller where the Eloquent query is being made.
After the namespace declaration, we add:
use App\Traits\GlobalUtility;
use Illuminate\Database\Eloquent\ModelNotFoundException;
And within the class:
use GlobalUtility;
5. Update the Query
Now we must ensure the query utilises a "orFail" method, like firstOrFail
or findOrFail
, then pop it into a try & catch blocks.
try
{
$dealer = $user->worksFor()->firstOrFail();
}
catch (ModelNotFoundException $e)
{
}
6. Configure the redirect
Within the catch statement, we add the redirect with a call to the trait method to grab the appropriate error. We'll set this as customError
, which will be a value available via the Session later on.
try
{
$dealer = $user->worksFor()->firstOrFail();
}
catch (ModelNotFoundException $e)
{
return redirect()->route('orders.history')->with([
'customError' => $this->getExceptionError('noDealerRelationship')
]);
}
7. Add check and fetch to the Blade file
Finally, we need to ask the app if the customError
value exists in the session, and if so, display.
{{-- Show custom defined errors (caught from exceptions) if they exist --}}
@if (Session::has('customError'))
<div class="alert alert-danger">
<strong>{{ Session::get('customError') }}</strong>
</div>
@endif
And that's it! Hope someone finds this useful :)
Posted on September 9, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.