Laravel Custom Request Validation

01 Dec, 2018 | 4 minutes read

Introduction

  • Laravel is PHP framework for web artisan. If you want to build and maintain robust web applications and APIs then Laravel is the way to do it. This framework is highly acknowledged for his build in and lightweight templating engine (Blade), MVC architecture support that helps for better performance, allowing good documentation with multiple built-in functions. His migration system for databases is very useful for re-creating new instances of the same project and decreases the possibility for losing data in the migration process.
  • Validation is one of the core features of any application including both client-side and server side. We need to take measures for checking if the user input is valid and suitable for the individual action. The server-side validation is used for processing the data sent from the client side with one of the many server-side languages, in our case PHP. This validation is important because even if the JavaScript is turned off in the browser this validation will fail and it can’t be easily bypassed by malicious users.

How is it done?

  • There are many ways to validate a request in Laravel and as mentioned before handling request validation is a crucial part of any application. Laravel like many other MVC frameworks can access details about the HTTP request for every action but Laravel takes one step further in allowing different types of requests to be defined based on the needs of the certain feature, so in order for the action to proceed these developer-defined rules need to be met. This is done with Laravel Form Request, a separate request class containing validation logic.

In order to create a new request class, the Artisan command listed below can be used:

php artisan make:request ClientRequest

When the command is finished executing a new Request Class with default structure is created in:

App\Http\Requests\ClientRequest

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

As we can see Laravel Form Request class comes with two default methods:

  • authorize() – This method is used for any authorization logic that needs to be applied to the current request.
  • rules() – Here all the server-side validation rules are written. Laravel has a list of pre-defined validation rules but the developer can also write custom rules for some specific usage.

In the ClientRequest that was created earlier, we can see how different validation rules can be used on different input fields and more than one rule can be applied to one input.

/**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        $rules = [
            "company_name" => "required",
            "legal_entity_id" => "required",
            "zip_code" => "required",
            "zip_number" => "required",
            "street" => "required",
            "city" => "required",
            "country_id" => "required",
            "language" => "required",
            "iban" => "required",
            "client_invoice_email_address" => "nullable|email",
            "minimal_credit_score" => ["nullable", "regex:/^(10|\d)(\.\d{1,2})?$/"],
            "factoring_limit" => "nullable|integer",
            " exact_reference_number " => "nullable|integer",
            "payout_term" => "nullable|integer",
        ];

        return $rules;
    }
  • Required fields (examples):
    • company_name
    • zip_code
    • language
  • Optional fields (examples):
    • client_invoice_email_address
    • exact_reference_number

For the optional fields if we want to add a validation rule (for example email) we must specify the filed as nullable otherwise it will be considered as required even though it wasn’t declared explicitly.

  • Custom validation (example):
    • minimal_credit_score

For this filed to be valid the regex, custom written by the developer needs to be met and if that is not the case the request will fail. If it is AJAX call it will result in 422 code – unprocessable entity.

Additional method messages() can be included where you can pass your own validation messages array for each of the validated fields. In this case according to the previous set of rules the validation messages array can look like this:

/**
     * Get the error messages for the defined validation rules.
     *
     * @return array
     */
    public function messages()
    {
        $msg = [
            'company_name.required' => formatValMsg('company_name', 'required_field'),
            'legal_entity_id.required' => formatValMsg('legal_entity_type', 'required_field'),
            'zip_code.required' => formatValMsg('zip_code', 'required_field'),
            'zip_number.required' => formatValMsg('zip_number', 'required_field'),
            "street.required" => formatValMsg('street', 'required_field'),
            "city.required" => formatValMsg('city', 'required_field'),
            "country_id.required" => formatValMsg('country', 'required_field'),
            "language.required" => formatValMsg('language', 'required_field'),
            "iban.required" => formatValMsg('iban', 'required_field'),
            "client_invoice_email_address.email" => formatValMsg('client_invoice_email_address', 'valid_email'),
            "minimal_credit_score.regex" => formatValMsg('minimal_credit_score', 'between_digits'),
            "factoring_limit.integer" => formatValMsg('factoring_limit', 'number_field'),
            "exact_reference_number.integer" => formatValMsg('exact_reference_number', 'number_field'),
            "payout_term.integer" => formatValMsg('payout_term', 'number_field'),
        ];

        return $msg;
    }

Here additionally a custom function formatValMsg() is created for formatting the messages according to the specific type and based of the locale of the logged user. The messages are stored in resources\lang\”user_locale”\validation.php file for every supported language.

After all the rules and messages are written and formatted if the server-side validation fails for some reason than the processed response will be displayed like in the picture

The function can be found in: App\Http\helpers.php

if (!function_exists('formatValMsg')) {

    /**
     * formatValMsg - Format the validation messages created in the back-end
     * and send to the front-end
     *
     * @param String $field - the validation field
     * @param String $val   - the type of the validation message
     * @return String       - formatted string message
     */
    function formatValMsg($field, $val)
    {
        $normalizedField = ucfirst(str_replace("_", " ", $field));
        $valMsg = strtolower(__(sprintf('validation.%s', $val)));
        return sprintf("%s : %s", $normalizedField, $valMsg);
    }
}	

After all the rules and messages are written and formatted if the server-side validation fails for some reason than the processed response will be displayed like in the picture below:

Conclusion:

  • These days, a quickly developing customer request prompts huge amounts of data that each competitive product should manage accurately and adequately. Data validation is the foundation of any professional application because it guarantees that the system works on clean, perfect, and helpful data. At first, it may seem unnecessary to make a separate request class for this purpose, but imagine putting all your validation logic in the same controller. It breaks the rule of the single responsibility principle and in this way, it is easier to maintain the code or even better if someone else is maintaining your code.