Skip to main content
  1. Studies & Thoughts/

ControlOverException

·908 words·5 mins·
Programming PHP Laravel

Introduction #

Laravel is a well known and very complete framework. With over 29k starts on github it has a solid and very opinionated vison about how web systems should be developed since defining things like Routing, Validation and Error handling as well as more complex subjects like Queues, Websockets and Caching. Besides the examples I mentioned - and there is even more if you take a look at their docs - they also have a rich ecosystem with a lot of tools to help developers with their job.

Today I’ll be focusing on two parts of the framework in this we will explore how error handling can be done together with Laravel’s validation system.

Custom Exceptions #

Digressing a little, one powerful thing we can do as developers is to use CustomExceptions on our code. Why? because exceptions unlocks the ability of having control over the flow of execution precisely by scoping the code into a block called try and if no exceptions is being throw GREAT, but, on the other hand if indeed an exception is thrown then we catch it in another block. In addition, exceptions add a lot of readability to the code base due to its simple and clear struture.

for instance, below an example - using pseudo code - hipotetical rest api endpoint that returns the user data by id.

function findUserById(id) {
	user = db.users.find(id)
	if (!user)
		throw new UserNotFoundException()
  
	return user
}

try {
	user = findUserById(1)
	return response.setData(user).setStatus(200)
} catch(UserNotFoundException $e) {
	return response.setStatus(404)
}

Isn’t that pretty clean? first we try to execute the findByUserId and if that works we just return what we get from that, on the other hand, if the user with that id does not exist then we throw and exception that will be catched by the catch block and from there we can handle it by sending a proper response as we need.

Also, a try catch block can have as many catch blocks we would need and its syntax is always defined same way with the word catch followed by parentesis and the type of exception that is will be catch.

for instance, let’s enhance the example above and assume that for some reason in this system users have privacy levels and depending on the privacy level I won’t allow that user data to be returned.

function findUserById($id) {
	user = db.users.find($id)
	if (!user)
		throw new UserNotFoundException()
  
	return user
}

function checkPrivacyLevel(user) {
	if (user.getPravacyLevel() === 'HIGH')
		throw new UserWithHighPrivacyLevelException()
}

try {
	user = findUserById(1)
	checkUserPrivacyLevel(user)
	
	return response.setData(user).setStatus(200)
} catch(UserNotFoundException $e) {
	return response.setStatus(404)
} catch(UserWithHighPrivacyLevelException$e) {
	return response.setStatus(403)
}

Nice, eh? Now we know a little about custom exceptions let’s see how Laravel’s allows us to use it.

Exception Handler #

Laravel ships with an exception handler to help people organize their custom exceptions. I won’t focus on all the properties and methods of the handler and neither will go deep in its inner guts to show how it works, specifically, I will focus on one specific method that we will use to handle exceptions for Laravel validation system.

Without further ado, let’s create a class called BadRequestException like the following.

use Exception;
use Illuminate\Http\JsonResponse;

class BadRequestException extends Exception {
	/**
	 *
	 * Returns 400 when a FormRequest fails
	 * any validation
	 *
	 */
	public function render(): JsonResponse
	{
		 return response()->json([], 400);
	}
}

By doing this Laravel’s exception handler will turn your exception into a JsonResponse by calling the render method. Now that we have our exception configured let’s take a look at how we can hook that up with Laravel’s FormRequest.

According to Laravel FormRequests

… are custom request classes that encapsulate their own validation and authorization logic.

In those classes, we have to implement a method called rules that will define the validation of your request object, something like the following:


/**
 * Get the validation rules that apply to the request.
 *
 * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
 */
public function rules(): array
{
	return [
		'title' => 'required|unique:posts|max:255',
		'body' => 'required',
	];
}

We can read about all that fun stuff in their docs. Right now let’s do something more fun. Let’s open this file and scroll till line 146… there we can see what happens when when any rule fails on the form request and… HA a ValidatioException is thrown!

Now how can we have controler over that? Well in order to take control over it we can override this method in our FormRequest class, something like this.


class UserFormRequest extends FormRequest
{
	// ... other methods above

	/**
	 * Get the validation rules that apply to the request.
	 *
	 * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
	 */
	public function rules(): array
	{
		return [
			'title' => 'required|unique:posts|max:255',
			'body' => 'required',
		];
	}

	/**  
	 * Handle a failed validation attempt. *
	 * @param  \Illuminate\Contracts\Validation\Validator  $validator  
	 * @return void  
	 *
	 * @throws BadRequestException 
	 */
	protected function failedValidation(Validator $validator): void
	{
		throw new BadRequestException($validator);
	}
}

Now the only thing we need to do is to tweak the BadRequestException class to make it receive the $validator as an parameter like below.

use Exception;
use Illuminate\Http\JsonResponse;

class BadRequestException extends Exception {
	private $validator;

	public function __construct(Validator $validator)
	{
		$this->validator = $validator;
	}

	/**
	 *
	 * Returns 400 when a FormRequest fails
	 * any validation
	 *
	 */
	public function render(): JsonResponse
	{
		 return response()->json([], 400);
	}
}

And now you have full control over your request validation. :-)

References #

https://laravel.com/docs/10.x/validation#form-request-validation https://laravel.com/docs/10.x/errors#renderable-exceptions https://github.com/laravel/framework/blob/be2ddb5c31b0b9ebc2738d9f37a9d4c960aa3199/src/Illuminate/Foundation/Http/FormRequest.php