Beyond Try-Catch: How Custom Exceptions Can Elevate Your Laravel Error Handling
Error management is a thing we often dont take very seriously when developing applications. But as bugs are part of the development, it is also very important to manage errors efficiently from user’s perspective. If we are working in a framework, it would be very good if the framework provides some default error or exception handling ways so that it becomes easy for the developers. Laravel is such framework which provides a very smart error handling ways. We might use Laravel log or try catch to do so, but alternatively we can use exceptions which is smarter. Laravel generates some default exception, whenever there is a bug or error occurs from a code, but to achieve that you need to keep APP_DEBUG=true in the environment. So, in the production, as debugging is off, users dont have any scope to understand what went wrong if some error occurs with exception. It just says the error code but not much information to understand better. Using custom exceptions will help you to test your code and generate more specific error messages so that you can identify the bug in quick time. Lets see how we can use custom exception for handling errors better in laravel applications.
Prepare the application
Lets setup a fresh new laravel application to learn about handling custom exception:
composer create-project --prefer-dist laravel/laravel my_laravel_app
cd my_laravel_app
Prepare the exception file
Lets say, in our application there is an ordering process and when user tries to make an order, if the requested quantity is negative or zero, in such case we want to show an error to the user.
Now we need to create an exception file with this command:
php artisan make:exception InvalidOrderException
This will create a new file InvalidOrderException under app/Exceptions directory. Lets modify the file like this:
<?php
namespace App\Exceptions;
use Exception;
class InvalidOrderException extends Exception
{
public function __construct($message = "Invalid order", $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
// Optionally, you can add custom rendering or reporting logic here
}
Throwing the Custom Exception
Now we can use our exception in anywhere we want to.
Lets make our OrderController to use the exception:
php artisan make:controller OrderController
And update the OrderController file like this:
<?php
namespace App\Http\Controllers;
use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function placeOrder(Request $request)
{
// Example condition for throwing the exception
if ($request->input('quantity') <= 0) {
throw new InvalidOrderException("Order quantity must be greater than zero.");
}
// Proceed with order placement logic
return response()->json(['message' => 'Order placed successfully!']);
}
}
So here, the placeOrder method is responsible for placing the order. We have placed our logic at first of the method as discussed, and if quantity is 0 or negative, we are throwing an exception with a custom message.
This will generate errors like below:
Handling the Exception
To handle the exception with JSON, we can add this code in register method from app/Exceptions/Handler.php:
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
use App\Exceptions\InvalidOrderException;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
//
];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register()
{
$this->renderable(function (InvalidOrderException $e, $request) {
return response()->json([
'error' => 'Invalid order',
'message' => $e->getMessage()
], 400);
});
}
}
Alternatively, you might update the InvalidOrderException, which will result same:
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Http\JsonResponse;
class InvalidOrderException extends Exception
{
/**
* Create a new InvalidOrderException instance.
*
* @param string $message
* @param int $code
* @param \Exception|null $previous
* @return void
*/
public function __construct($message = 'Invalid order', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
/**
* Report the exception.
*
* @return bool|null
*/
public function report()
{
// You can add custom reporting logic here if needed
}
/**
* Render the exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function render($request)
{
return response()->json([
'error' => 'Invalid order',
'message' => $this->getMessage()
], 400);
}
}
Lets test the code
You can now test the exception handling by making a request to the placeOrder method in OrderController.
For example, you can try this using a tool like Postman or cURL:
curl -X POST http://your-laravel-app.test/order -d "quantity=0"
Both of the process above will generate output like below:
{
"error": "Invalid order",
"message": "Order quantity must be greater than zero."
}
Summary
In summary, using exception will help you to get clear information when debugging. Even users from the application can also understand better and report what went wrong exactly. Instead of try catch or logging info, we can use exceptions for better error handling.
So thats all from this post today. Hope you found it helpful. Please let me know your thoughts in the comments below and give a love to this article.
I wish you a happy day.
Comments