Validating your data with Respect Validation

Younes Rafie
Younes Rafie
Share

Validation is an important aspect of every application’s interaction with data. Instead of reinventing the wheel every time, the community collaborated on some useful packages like Symfony, Laravel, Zend, etc. In this article, we’re going to introduce a lesser known package called Respect Validation, which provides some nice new features. Let’s get started.

When using a framework, there is a great chance that you have a validation component shipped with it. The Symfony Validator Component provides a set of standard validation rules that you’ll need for your application.

class UserSubscriptionForm
{
    protected $email;

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('email', new \Assert\Email([
            'message' => 'Invalid email.'
        ]));
    }
}

I’m sure your subscription form contains more than just an email, but let’s keep it simple. Inside your controller you’ll have to trigger the validation like so:

public function store(Request $request){
    $userSubscriptionForm = UserSubscriptionForm::fromInput($request->all());
    $validator = $this->get('validator');
    $errors = $validator->validate($userSubscriptionForm);

    if (count($errors) > 0) {
        // redirect with errors
    }
    
    // return success
}

After creating a new UserSubscriptionForm from the user input, we will trigger the validation and collect the validation errors if we found any.

Laravel also ships with the Illuminate validation package. It does the same job, but in a different way. Let’s use our previous validation example and convert it to Laravel.

class UserSubscriptionController extends Controller
{
    public function store(Request $request)
    {
        $validator = \Validator::make(
            [
                'email' => $request->get('email')
            ],
            [
                'email' => 'required|email'
            ]
        );
    
        if($validator->fails()){
            $errors = $validator->messages();
    
            // redirect with errors
        }
        
        // return success
    }
}

The Zend validator package is not much different from the others. For the same previous example, we can do the following:

class UserSubscriptionController extends Controller
{
    public function store(Request $request)
    {
        $validator = new Zend\Validator\EmailAddress();

        if(!$validator->isValid($request->get('email'))){
            $errors = $validator->getMessages();

            // redirect with errors
        }

        // return success
    }
}

I’m sure you’re already familiar with at least one of the packages mentioned above. In this article, we are going to introduce Respect/Validation and we will highlight the main differences from the other packages.

Respect Validation

The Respect validator introduces a simple way of validating your data. Let’s start by completing the same previous example.

class UserSubscriptionController extends Controller
{
    public function store(Request $request)
    {
        $emailValidator = \Respect\Validation\Validator::email();

        if (!$emailValidator->validate($email)) {
            // redirect with errors
        }

        // return success
    }
}

So, nothing new! The first thing you’ll notice is that we didn’t retrieve the list of errors after a validation failure. To retrieve the list of errors, we need to use the assert method which throws an exception containing the list of error messages.

class UserSubscriptionController extends Controller
{
    public function store(Request $request)
    {
        $emailValidator = \Respect\Validation\Validator::email();

       try{
           $emailValidator->assert($request->get('email'));
       }
       catch(\Respect\Validation\Exceptions\NestedValidationExceptionInterface $ex){
           $errors = $ex->getMainMessage();
           // redirect with errors
       }

        // return success
    }
}

We also have the ability to add multiple rules to the same value by chaining methods.

Validator::string()->noWhitespace()->length(4, 10);
Validator::numeric()->between(5, 10);
Validator::date()->between(5, 10);

Another way to validate multiple rules is to use the allOf rule which accepts a list of rules.

$inputValidator = \Respect\Validation\Validator::allOf(
    new String(),
    new Length(4, 10)
);

You probably need to validate some data to match at least one of your business rules. Let’s take the login form as an example where the user can either enter their email address or username. The username must be alpha numeric and between 4 and 16 characters long. You can check the documentation for more details about the list of available rules.

$usernameOrEmailValidator = \Respect\Validation\Validator::oneOf(
    new \Respect\Validation\Rules\Email(),
    \Respect\Validation\Validator::string()->alnum()->noWhitespace()->length(4, 16)
);

One feature that you don’t often see in validation packages is the rule negation functionality. You can specify the rules that you don’t want to match, for example.

$inputValidator = \Respect\Validation\Validator::not(\Respect\Validation\Validator::numeric());

Custom Error Messages

As mentioned earlier, when an assertion exception is thrown, you can get error messages using one of the following methods.

  • getFullMessage: returns a general error message with a list of the failing rules. Asserting Validator::email()->assert('notAValidEmail') will throw the following message.
\-These rules must pass for "notAValidEmail"
  \-"notAValidEmail" must be valid email
  • getMainMessage: returns a general error message without specifying the failing rules. The email example returns These rules must pass for "notAValidEmail.
  • findMessages: accepts an array as a parameter containing the list of messages for the failing rules.
$this->messages = [
    'alpha'                 => '{{name}} must only contain alphabetic characters.',
    'alnum'                 => '{{name}} must only contain alpha numeric characters and dashes.',
    'numeric'               => '{{name}} must only contain numeric characters.',
    'noWhitespace'          => '{{name}} must not contain white spaces.',
    'length'                => '{{name}} must length between {{minValue}} and {{maxValue}}.',
    'email'                 => 'Please make sure you typed a correct email address.',
    'creditCard'            => 'Please make sure you typed a valid card number.',
    'date'                  => 'Make sure you typed a valid date for the {{name}} ({{format}}).',
    'password_confirmation' => 'Password confirmation doesn\'t match.'
];

// inside the try catch block

try {
    $this->rules[$input]->assert($value);
} catch (\Respect\Validation\Exceptions\NestedValidationExceptionInterface $ex) {
    dump($ex->findMessages($this->messages));
}

This will return a custom message if one the failing rules exists inside our messages array. You can read more about custom messages in the documentation.

To make things a little more practical, we will be validating a user subscription form. The form contains a set of fields to identify the user, and a small section for the billing info.

User Subscription Validation

I will be using Laravel for my example, but I think it doesn’t matter in this case since we don’t use any Laravel magic. You can start by adding the package using composer: composer require respect/validation. The user subscription form will look like this:

User Subscription Form

// resources/views/home.blade.php

<form action="/send" method="POST">
    <legend>Profile</legend>

    <input class="form-control" name="username" type="text" autofocus="" value="{{\Input::old('username')}}">

    <input class="form-control" name="email" type="email" value="{{\Input::old('email')}}">

    <input class="form-control" name="password" type="password" value="">

    <input class="form-control" name="password_confirmation" type="password" >
            
    <legend>Billing</legend>

    <input class='form-control' size='4' type='text' name="cardHolderName" value="{{\Input::old('cardHolderName')}}">

    <input class='form-control' size='20' type='text' name="cardNumber" value="{{\Input::old('cardNumber')}}">

    <input class='form-control' size='20' type='text' name="billingAddress" value="{{\Input::old('billingAddress')}}">

    <input class='form-control' size='4' type='text' name="cvc" value="{{\Input::old('cvc')}}">

    <input class='form-control' size='2' type='text' name="expirationMonth" value="{{\Input::old('expirationMonth')}}">

    <input class='form-control' size='4' type='text' name="expirationYear" value="{{\Input::old('expirationYear')}}">

    <button class="btn btn-primary">Join</button>
</form>
// app/Http/routes.php

Route::get('/', 'UserSubscriptionController@index');
Route::post('/send', 'UserSubscriptionController@send');

Our UserSubscriptionController class will have two methods. One for printing the form and another for processing the form submission. We will create another separate class to process the form validation.

// app/Http/Controllers/UserSubscriptionController

class UserSubscriptionController extends Controller
{
    protected $userSubscription;

    public function __construct(UserSubscriptionValidator $userSubscription)
    {
        $this->userSubscription = $userSubscription;
    }

    public function index()
    {
        return view('home');
    }

    public function send(Request $request, Redirector $redirector)
    {
        $inputs = $request->all();
        $isValid = $this->userSubscription->assert($inputs);

        if (!$isValid) {
            return $redirector->back()->withInput()->with('errors', $this->userSubscription->errors());
        }

        return "Everything is Good!";
    }
}

The App\Validation\UserSubscriptionValidator class will be exposed through two methods (assert and errors).

// app/Validation/UserSubscriptionValidator.php

use \Respect\Validation\Validator as V;

class UserSubscriptionValidator
{
    /**
     * List of constraints
     *
     * @var array
     */
    protected $rules = [];
    
    /**
     * List of customized messages
     *
     * @var array
     */
    protected $messages = [];

    /**
     * List of returned errors in case of a failing assertion
     *
     * @var array
     */
    protected $errors = [];

    /**
     * Just another constructor
     *
     * @return void
     */
    public function __construct()
    {
        $this->initRules();
        $this->initMessages();
    }
    
    /**
     * Set the user subscription constraints
     *
     * @return void
     */
    public function initRules()
    {
        $dateFormat = 'd-m-Y';
        $now = (new \DateTime())->format($dateFormat);
        $tenYears = (new \DateTime('+10 years'))->format($dateFormat);

        $this->rules['username'] = V::alnum('_')->noWhitespace()->length(4, 20)->setName('Username');
        $this->rules['password'] = V::alnum()->noWhitespace()->length(4, 20)->setName('password');
        $this->rules['email'] = V::email();
        $this->rules['cardHolderName'] = V::alpha()->setName('Card holder name');
        $this->rules['cardNumber'] = V::creditCard()->setName('card number');
        $this->rules['billingAddress'] = V::string()->length(6)->setName('billing address');
        $this->rules['cvc'] = V::numeric()->length(3, 4)->setName('CVC');
        $this->rules['expirationDate'] = V::date($dateFormat)->between($now, $tenYears)->setName('expiration date');
    }
}

Our constructor calls the initRules method which sets the validation constraints. The setName method value is injected into the error messages template. The initMessages method sets the list of customized messages; this can be moved outside the class if you’re going to use them again for another form.

// app/Validation/UserSubscriptionValidator.php

/**
 * Set user custom error messages
 *
 * @return void
 */
public function initMessages()
{
    $this->messages = [
        'alpha'                 => '{{name}} must only contain alphabetic characters.',
        'alnum'                 => '{{name}} must only contain alpha numeric characters and dashes.',
        'numeric'               => '{{name}} must only contain numeric characters.',
        'noWhitespace'          => '{{name}} must not contain white spaces.',
        'length'                => '{{name}} must length between {{minValue}} and {{maxValue}}.',
        'email'                 => 'Please make sure you typed a correct email address.',
        'creditCard'            => 'Please make sure you typed a valid card number.',
        'date'                  => 'Make sure you typed a valid date for the {{name}} ({{format}}).',
        'password_confirmation' => 'Password confirmation doesn\'t match.'
    ];
}

The {{name}} tag will be updated with the corresponding name passed previously using the setName method. Some rules contain more variables, like {{minValue}} and {{maxValue}} for the length rule. Same thing for the date rule. The next step is to define our assert method to perform the actual validation.

/**
 * Assert validation rules.
 *
 * @param array $inputs
 *   The inputs to validate.
 * @return boolean
 *   True on success; otherwise, false.
 */
public function assert(array $inputs)
{
    $expirationMonth = array_get($inputs, 'expirationMonth');
    $expirationYear = array_get($inputs, 'expirationYear');
    $inputs['expirationDate'] = '01-' . $expirationMonth . '-' . $expirationYear;

    foreach ($this->rules as $rule => $validator) {
        try {
            $validator->assert(array_get($inputs, $rule));
        } catch (\Respect\Validation\Exceptions\NestedValidationExceptionInterface $ex) {
            $this->errors = $ex->findMessages($this->messages);
            return false;
        }
    }

    $passwordConfirmed = $this->assertPasswordConfirmation($inputs);

    return $passwordConfirmed;
}

The first step is to create a valid date from the expiration month and year. This will help us to easily validate it against actual date values. We loop through the list of validation rules and test if the user input matches the defined rule. If an assertion fails, we set the errors message attribute and return false to the controller.

Currently, the package doesn’t provide a rule to automaticaly validate password confirmation. The assertPasswordConfirmation method will only test if the passed values are equal and set the error message in case of failure.

public function assertPasswordConfirmation(array $inputs)
{
    $passwordConfirmation = array_get($inputs, 'password_confirmation');
    if ($inputs['password'] !== $passwordConfirmation) {
        $this->errors['password_confirmation'] = $this->messages['password_confirmation'];
        return false;
    } else {
        return true;
    }
}

The last part is to return the errors to the user if it fails, and we also need to update our home template to print errors in case of failure.

public function errors()
{
    return $this->errors;
}
// home.blade.php

@if(\Session::has('errors'))
    <div class="alert alert-danger">
        @foreach(\Session::get('errors') as $error)
            @if(!empty($error))
                <p>{{$error}}</p>
            @endif
        @endforeach
    </div>
@endif

Validation test

Creating Your Own Rules

You can create your own business rules and use the with method to add your namespace to the lookup array. You can check the documentation for more details about creating your own constraints.

Respect\Validation\Validator::with('App\\Validation\\User\\Rules\\');

If you found that the rules you’re trying to use exist in a Zend or Symfony validation package, you can directly use them using one of the following methods.

Respect\Validation\Validator::sf('Language')->validate('Arabic');
Respect\Validation\Validator::zend('EmailAddress')->validate('me@gmail.com');

Conclusion

This article briefly introduced the Respect validation package. We saw how simple the API is and how it integrates with other validators like Symfony and Zend. It’s also easy to extend and add new rules to the validation process. If you’ve never used it before, I really encourage you to give it a try. If you’ve already used it in some of your projects, I would really love to know what you think about it and how it fits your use case in the long term – let us know in the comments below!

Frequently Asked Questions (FAQs) about Data Validation

What is the importance of data validation in data analysis?

Data validation plays a crucial role in data analysis. It ensures the accuracy, consistency, and reliability of data. Without proper validation, the data used in analysis might be incorrect, leading to inaccurate results and conclusions. It also helps in identifying and correcting errors at an early stage, saving time and resources in the long run. Moreover, data validation enhances the credibility of the data, making the analysis more reliable and trustworthy.

How does data validation differ from data verification?

While both data validation and data verification aim to ensure the accuracy of data, they are different in their approach. Data validation checks whether the data meets certain predefined criteria or conditions. It ensures that the data is clean, correct, and useful. On the other hand, data verification is the process of checking if data has been accurately transferred from one source to another without any corruption or loss.

What are the different types of data validation?

There are several types of data validation, including range check, format check, consistency check, and existence check. Range check ensures that the data falls within a specified range. Format check verifies that the data is in the correct format. Consistency check ensures that the data is logical and makes sense. Existence check verifies that the data actually exists.

What are the steps involved in data validation?

The process of data validation involves several steps. First, the data is collected from various sources. Then, it is cleaned to remove any errors or inconsistencies. After that, the data is checked against predefined criteria or conditions to ensure its validity. If any errors are found, they are corrected. Finally, the validated data is used for analysis or decision-making.

How can data validation improve the quality of data?

Data validation can significantly improve the quality of data by ensuring its accuracy, consistency, and reliability. It helps in identifying and correcting errors at an early stage, preventing them from affecting the results of data analysis. Moreover, data validation enhances the credibility of the data, making it more trustworthy and reliable.

What are the challenges in data validation?

Data validation can be challenging due to several reasons. One of the main challenges is the large volume of data that needs to be validated. This can be time-consuming and resource-intensive. Another challenge is the complexity of data, which can make it difficult to define appropriate validation rules. Moreover, data validation requires a high level of expertise and knowledge, which may not be available in all organizations.

Can data validation be automated?

Yes, data validation can be automated using various tools and technologies. Automation can significantly speed up the validation process and reduce the risk of human errors. However, it’s important to note that automated validation is not foolproof and may not be able to catch all errors. Therefore, it’s often used in conjunction with manual validation.

What is the role of data validation in data security?

Data validation plays a crucial role in data security. By ensuring the accuracy and consistency of data, it helps prevent data corruption and loss. Moreover, data validation can help detect and prevent security threats such as data breaches and cyber attacks. For example, it can be used to validate user inputs and prevent SQL injection attacks.

How does data validation contribute to decision-making?

Data validation contributes to decision-making by ensuring that the data used for analysis is accurate, consistent, and reliable. This leads to more accurate results and conclusions, enabling better decision-making. Moreover, data validation enhances the credibility of the data, making the decisions based on it more trustworthy and reliable.

What are the best practices for data validation?

Some of the best practices for data validation include defining clear validation rules, using a combination of manual and automated validation, regularly reviewing and updating the validation rules, and training the staff on the importance of data validation and how to perform it effectively. Moreover, it’s important to document the validation process and results for future reference and accountability.