A comprehensive guide on how to design future-proof controllers: Part 2
Tags: webdev, tutorial, php
Published: 2022-04-02 11:23:39

## Introduction

This article is the second part of a series of articles where we talk about how separating code based on functionality can improve the quality of your codebase. If you have not read the first part, check it out [here](https://dev.to/tosinibrahim96/a-comprehensive-guide-on-how-to-design-future-proof-controllers-part-1-4ll4).

This article will focus on separating the request validation concern from the rest of the code in the Controller. I will be using Laravel in this tutorial. Still, you can apply the ideas we will be discussing here to any programming language that supports any form of code separation into packages, modules, etc., in such a way that you can use code written in `file A` in `file B`.


## Prerequisites
- Reading the first part of this article [here](https://dev.to/tosinibrahim96/a-comprehensive-guide-on-how-to-design-future-proof-controllers-part-1-4ll4) to understand some concepts and terminologies
- Knowledge of Object-Oriented Programming (OOP) is required
- Basic knowledge of [Laravel](https://laravel.com/)
- If you want to test the code on your system, then you **MUST **have [PHP](https://www.php.net/manual/en/install.php) installed on your system


## Let's dive in 🚀

You can find all the code from this article [here](https://github.com/tosinibrahim96/concerns-demo).

**What we currently have** <br>
Let's look at a simple controller function that adds a new user to the database during a registration process. I have added comments to illustrate how we can identify the three concerns involved in the register function.

```
  /**
   * create a new account
   * @param \Illuminate\Http\Request $request
   * @return \Illuminate\Http\Response
   */
  public function register(Request $request)
  {
    /*----Concern (Request validation)----*/
    $validator = Validator::make($request->all(), [
      'email' => 'required|email|unique:users',
      'name' => 'required|string',
      'password' => 'required|min:6'
    ]);

    if ($validator->fails()) {
      /*----Concern (Sending response to the client)----*/
      return response()->json(
        [
          "status" => false,
          "message" => $validator->errors()
        ],
        422
      );
    }

    /*----Concern (Handling business logic (In this case, hashing the user's password and interaction with database)----*/
    $password = Hash::make($request->password);
    $user = User::create([
      'name' => $request->name,
      'password' => $password,
      'email' => $request->email
    ]);

    /*----Concern (Sending response to the client)----*/
    return response()->json(
      [
        "status" => true,
        "message" => 'User account created successfully',
        "data" => $user
      ],
      201
    );
  }
``` 

## Separating the Request Validation Concern 


![separation.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1644901361473/Bzp-o9JId.gif)

We will discuss two approaches to separate the Request validation concern from the Controller.

- The first approach involves building a request validation class and writing all your validation logic in the validation class. The Class needs to contain some `public` methods to ensure that the Controller can execute the validation logic.
- The second approach is called [Form request validation](https://laravel.com/docs/8.x/validation#form-request-validation). This approach is specific to Laravel, and the validation class has to follow a particular pattern. It's a powerful feature.

### Approach 1: Building a Request Validation Class


![building.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1644899837341/aYjcDYcyd.gif)


- Create a folder called `Validators` inside the `app/Http` directory 
- Create a new file called `AuthValidator.php` in the newly created `Validators` folder
- The code below should be the content of the `AuthValidator.php` file

```
<?php

namespace App\Http\Validators;

use Illuminate\Support\Facades\Validator;


class AuthValidator
{
  protected $errors = [];

  /**
   * validate user registration data
   *
   * @param  array $requestData
   * @return \App\Http\Validators\AuthValidator
   */
  public function validateRegistrationData(array $requestData)
  {
    $validator = Validator::make($requestData, [
      'email' => 'required|email|unique:users',
      'name' => 'required|string',
      'password' => 'required|min:6'
    ]);

    $this->errors = $validator->errors();

    return $this;
  }


  /**
   * get the errors that occurred during 
   * validation
   *
   * @return array
   */
  public function getErrors()
  {
    return $this->errors;
  }
}
```
**What exactly does the code do? 😕**
- First, we have a property in the Class called `$errors` and set the value as an empty array
- Then, we create a method called `validateRegisterationData(),` which will be responsible for executing the actual validation logic
- The result of the validation check (`$validator->errors()`) is stored in the `$errors` property of the current object. If all the data in the request are valid and no validation error(s) occurred, the validator returns an empty array.
- Finally, we return the current object (`$this`)
- The second method, `getErrors()` simply returns the `$errors` property for the current object. This property would contain the actual errors that occurred during validation or an empty array if no errors occurred

**How do we use the AuthValidator in the Controller? 🤔**<br>

Create a constructor method (`__construct()`) in the AuthController class and pass the Authvalidator class as a parameter in the constructor. Then the value of the parameter is stored in a class property.

```
class AuthController extends Controller
{

  protected $authValidator;
  
  /**
  * __construct
  * 
  *  @param \App\Http\Validators\AuthValidator $authValidator
   * @return void
   */
  public function __construct(AuthValidator $authValidator)
  {
   $this->authValidator = $authValidator;
    
  }
``` 

Now we can remove the existing validation logic in the `register()` method of the Controller and use the logic in the AuthValidator class we created ☺️.

```
  /**
   * create a new account
   * @param \Illuminate\Http\Request $request
   * @return \Illuminate\Http\Response
   */
  public function register(Request $request)
  {
    $errors = $this->authValidator
      ->validateRegistrationData($request->all())
      ->getErrors();
    
    if (count($errors)) {
      return response()->json([ "status" => false,"message" => $errors],
        422
      );
    }

```
Since the `validateRegistrationData()` method returns an object, we can immediately execute the `getErrors()` method on the returned object without assigning it to a variable first. This pattern is called [Fluent interface](https://designpatternsphp.readthedocs.io/en/latest/Structural/FluentInterface/README.html). `count($errors)` checks if there is at least one element in the errors array returned from the validator and returns a response to the client if any error exists.

With the code designed this way, the Controller does not care how the validation logic works. The Controller knows that the validator will do its job and return a response after completing the validation process. Then the final response is sent from the Controller. So we can add, remove or modify the validation logic, and the Controller still works perfectly without any issues. Sweet right. If you think this is great, let's look at the second method, which is using Laravel Form Requests.


### Approach 2: Using Laravel Form Requests


![laravelvalidation.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1644899979881/XCA6MnWka.gif)

Form requests are custom request classes containing their validation logic, just like we did in the first approach. Laravel Form requests are also awesome because they contain authorization logic by default. To create a form request class, you may use the `make:request` artisan command provided by Laravel.

```
php artisan make:request CreateNewUserRequest
```

Laravel will generate a form request class in the `app/Http/Requests` directory with the name passed as the file name. In our case, a new file, `CreateNewUserRequest.php` will be created.

By default, Form request classes generated by Laravel come with two methods, `authorize()` and `rules()`. The `authorize()` method contains the logic that determines if the user making that request has the permission to do so. Of course, you can modify the logic to fit your specific use case at any time. In our case, we do not require any special permission or authorization for the `CreateNewUserRequest` Class, so we return true.


```

/**
   * Determine if the user is authorized to make this request.
   *
   * @return bool
   */
  public function authorize()
  {
    return true;
  }

```

On the other hand, the `rules()` method returns the validation rules that should apply to the request's data.

```

/**
   * Get the validation rules that apply to the request.
   *
   * @return array
   */
  public function rules()
  {
    return [
      'email' => 'required|email|unique:users',
      'name' => 'required|string',
      'password' => 'required|min:6'
    ];
  }

```

**How do we use the rules in the CreateNewUserRequest class in the Controller? 🤔**<br>

- Change the type of the parameter in the register function from `\Illuminate\Http\Request` to `\App\Http\Requests\CreateNewUserRequestwith`(this is the path to the Laravel form request class we just built)
- Remove the validation logic in the `register()` function
- Since Laravel internally checks for errors and returns an appropriate response when using Form requests, we do not need an `if` statement to check if the validation failed

```
/**
   * create a new account
   * @param App\Http\Requests\CreateNewUserRequest $request
   * @return \Illuminate\Http\Response
   */
  public function register(CreateNewUserRequest $request)
  {
    /*--------Concern 2 (Business logic execution)------------------*/
    $password = Hash::make($request->password);
    $user = User::create([
      'name' => $request->name,
      'password' => $password,
      'email' => $request->email
    ]);

    /*--------Concern 3(Response formatting and return)------------------*/
    return response()->json(
      [
        "status" => true,
        "message" => 'User account created',
        "data" => $user
      ],
      201
    );
  }
```
And voila! Our Controller has absolutely no idea about the validation logic, and changes to the validations rules do not affect the Controller. So once again, we can change the validation logic however we want, and the Controller will remain the same and work perfectly.

### Let's explore Form Requests a little further 💪🏿
Here is a sample error response from the `CreateNewUserRequest` class after sending a request with an empty value as the `name` of the new user

```
{
    "message": "The given data was invalid.",
    "errors": {
        "name": [
            "The name field is required."
        ]
    }
}
```

Now, let's assume that due to our team's convention, we need to change the structure of the error response sent back to the client after a failed validation. We can achieve this by overriding the default `failedValidation()` method in all Laravel form request classes that extend `FormRequest`, just like our `CreateNewUserRequest` Class.

Now we have an extra method, `failedValidation()`, that looks like this.

```
/**
   * Handle a failed validation attempt.
   *
   * @param  \Illuminate\Contracts\Validation\Validator  $validator
   * @return void
   *
   * @throws \Illuminate\Validation\ValidationException
   */
  protected function failedValidation(Validator $validator)
  {
    throw new HttpResponseException(
      response()->json(
        [
          'status' => false,
          'message' => 'Validation errors occurred',
          'errors' => $validator->errors(),
        ],
        422
      )
    );
  }
```
After making our changes on how we want the error message to be structured, our response looks like this.


```
{
    "status": false,
    "message": "Validation errors occurred",
   "errors": {
        "name": [
            "The name field is required."
        ]
    }
}
```



## What's next? 💭
In the next part of this series, we will be looking at how to use `Actions` to separate business logic away from the Controller. Believe me, that would be awesome, and you don't want to miss it.

## Quick recap 🔄
- The controllers in your codebase should know what validation class to call to a particular request but have zero knowledge about the validation rules that apply to the request's data. That is the responsibility of Validators
- You can build your validator class or use Laravel form requests. I don't think any approach is better than the other. It all depends on whichever approach you prefer
- The Laravel form request class can also handle user authorization even before checking if the data from the client passes all the validation rules, and this is done via the `authorize()` method
- The advantage of building your validators is that your methods can follow any naming convention you want. However, when using Laravel Form requests, the method handling the data validation must be named `rules` while the method that handles authorization must be named `authorize`.


## It's a wrap 🎉
You can check out all the code in this article on Github. After reading this, I sincerely hope that you have learned something, no matter how small. If you did, kindly drop a thumbs up.
Thanks for sticking with me till the end. If you have any suggestions or feedback, kindly drop them in the comment section. Enjoy the rest of your day. Bye 😊.