In the first five posts of this series we have been talking about key components for any PHP framework from the point of view of their internals, such as HttpFoundation to abstract the HTTP protocol, HttpKernel to convert a Request into a Response, Routing to map requests to controllers, EventDispatcher to add reusability and extensibility, and Config to load and validate configuration values. This time we’ll delve deeper in the user space to describe a component that is for specific apps rather than just for frameworks. Today’s topic will be the Validator component.

It is not always easy to know what and how to validate a given input

The problem

Every single application that accepts data from a source must implement some sort of validation. It does not matter that your data comes from users or from an online API, you always run the risk your data might be tainted.

We all know the catastrophic consequences we can face if we don’t filter input data, from inconsistency in our database to security disasters caused by SQL injections, Cross-Site Scripting (XSS) or Cross-Site Request Forgery (CSRF), to name just a few. Even though the Validator component responsibilities fall under the first set of problems – to keep data consistency -, any non-filtered input data can trigger security issues.

Any application must validate input data. Some of this validations like emails, urls or ISBNs are so common that is a waste of time to code them again, as probably someone else has already made it before. The Validator component provides an extensive list of validations and a way to use them easily.

Architecture

The component it is based on the JSR-303 specification and each validation is composed of one or more constraints, which are validated against a validator. For example, in your database, your employees table must have only email addresses from your company, so you can use the default email constraint to check that the given address is valid and a second constraint that checks the domain. With the Validator component, this is done in two parts:

  • Constraints
    A constraint describes the rule. It is just a PHP object that makes an assertive statement, such as “the email must be valid”.
  • Validators
    Validators implement the validation logic. In the email example, the validator class will use filter_var($email, FILTER_VALIDATE_EMAIL) to check if the email is valid.

Simple example

<?php 

use Symfony\Component\Validator\Validation; 
use Symfony\Component\Validator\Constraints\Range; 

include_once __DIR__. '/vendor/autoload.php'; 

$validator = Validation::createValidator(); 
$constraint = new Range(array(
    'min' => 1,
    'max' => 10
));

$violations = $validator->validateValue(15, $constraint);

The $violations variable will have a ConstraintViolationList object with one ConstraintViolation object for each violation. If there are no violations, the list will be empty, but it is important to remember that for consistency, we will always get a ConstraintViolationList object, not null, false or 0.

In the last example, the $violations variable will contain one violation:

class Symfony\Component\Validator\ConstraintViolationList#10 (1) {
  private $violations =>
  array(1) {
    [0] =>
    class Symfony\Component\Validator\ConstraintViolation#13 (8) {
      private $message =>
      string(32) "This value should be 10 or less."
      private $messageTemplate =>
      string(41) "This value should be {{ limit }} or less."
      private $messageParameters =>
      array(2) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      int(11)
      private $propertyPath =>
      string(0) ""
      private $invalidValue =>
      int(11)
      private $code =>
      NULL
    }
  }
}

The ConstraintViolationList class implements Traversable, Countable and ArrayAccess interfaces. That means that we can work with the object like it was an array:

var_dump(count($violations));
var_dump(empty($violations));

foreach ($violations as $violation) {
    var_dump($violation->getMessage());
}
int(1)
bool(false)
string(32) "This value should be 10 or less."

Built-in validations

The component is shipped with the most commonly-needed constraints. Basic constraints to check that an email is valid – even checking host and MX records if needed -, the length of a string, number ranges, list of choices and many more are available out of the box.

Internationalization

We will talk more about the Translation component in future posts, but for now you must know that any object implementing the TranslatorInterface interface can be injected into the validator. The trans method of the object will be called to get the translation. Let’s see an example:

<?php 

use Symfony\Component\Validator\Validation; 
use Symfony\Component\Validator\Constraints\Range; 
use Symfony\Component\Translation\Loader\XliffFileLoader; 
use Symfony\Component\Translation\Translator; 

include_once __DIR__. '/vendor/autoload.php'; 

$translator = new Translator('es_ES'); 
$translator->addLoader('xliff', new XliffFileLoader());
$translator->addResource('xliff', __DIR__ . '/vendor/symfony/validator/Symfony/Component/Validator/Resources/translations/validators.es.xlf', 'es');

$validator = Validation::createValidatorBuilder()
    ->setTranslator($translator)
    ->getValidator();

$rangeConstraint = new Range(array(
    'min' => 1,
    'max' => 10
));

$violations = $validator->validateValue(11, $rangeConstraint);

var_dump($violations[0]->getMessage());

In this example, we want to show error messages in Spanish. To do so, we create the translator object and configure it to use the default XLIFF translation files provided by the component. Obviously, for simplicity, this is a dirty way to reference the file and, based on your application, you should find a better way to do it without relying on a Composer folder called vendor or that the autoload method is going to be PSR-0. Finally, we inject the translator and use the validator as usual. We get as the output the error message in Spanish:

string(35) "Este valor debería ser 10 o menos."

Validating objects

Most of the times, you will need to validate full objects instead of single values before persisting it in the database. How does the validator know what rules apply to each property? We need to create a mapping that links class properties with sets of constraints. It can be done using YAML, XML, annotations or PHP, in our example we will do it using YAML:

RaulFraile\Foo:
    properties:
        email:
            - NotBlank: ~
            - Email: ~
        age:
            - Range:
                min: 18

Once we have the mapping we need to tell the validator what kind of mapping we are using and where to find it:

<?php 

use Symfony\Component\Validator\Validation; 
use Symfony\Component\Validator\Constraints\Range; 
use RaulFraile\Foo; 

include_once __DIR__. '/vendor/autoload.php'; 

$obj = new Foo(); $obj->setEmail('raul@servergrove');
$obj->setAge(17);

$validator = Validation::createValidatorBuilder()
    ->addYamlMapping(__DIR__ . '/src/RaulFraile/validation/Foo.yml')
    ->getValidator();

$violations = $validator->validate($obj);

var_dump($violations);

Finally, we call the validate method – not validateValue – and get the list of violations as usual. In this case, the email is not correct and the age is less than the minimum allowed:

class Symfony\Component\Validator\ConstraintViolationList#11 (1) {
  private $violations =>
  array(2) {
    [0] =>
    class Symfony\Component\Validator\ConstraintViolation#25 (8) {
      private $message =>
      string(40) "This value is not a valid email address."
      private $messageTemplate =>
      string(40) "This value is not a valid email address."
      private $messageParameters =>
      array(1) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      class RaulFraile\Foo#2 (2) {
        ...
      }
      private $propertyPath =>
      string(5) "email"
      private $invalidValue =>
      string(16) "raul@servergrove"
      private $code =>
      NULL
    }
    [1] =>
    class Symfony\Component\Validator\ConstraintViolation#29 (8) {
      private $message =>
      string(32) "This value should be 18 or more."
      private $messageTemplate =>
      string(41) "This value should be {{ limit }} or more."
      private $messageParameters =>
      array(2) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      class RaulFraile\Foo#2 (2) {
        ...
      }
      private $propertyPath =>
      string(3) "age"
      private $invalidValue =>
      int(17)
      private $code =>
      NULL
    }
  }
}

Custom validations

The list of built-in constraints is huge and keeps growing, but if you need custom validations, it’s as easy as creating the corresponding classes for the constraint and the validation logic. For example, let’s create a simple validation to check that a programming language starts with ‘p’, or is in a fixed list of allowed languages – I know it is weird, but it’s just an example :) -.

First, we create the constraint, which defines the error message and the list of available programming languages:

<?php

namespace RaulFraile\Constraints;

use Symfony\Component\Validator\Constraint;

class ProgrammingLanguage extends Constraint
{
    public $message = 'The string "%string%" is not an accepted programming language';
    public $languages = array('php', 'javascript');
}

Then, the validation logic:

<!?php 

namespace RaulFraile\Constraints; 

use Symfony\Component\Validator\Constraint; 
use Symfony\Component\Validator\ConstraintValidator; 

class ProgrammingLanguageValidator extends ConstraintValidator {
    
    public function validate($value, Constraint $constraint) 
    {
        if (!(!empty($value) && $value[0] == 'p') && !in_array($value, $constraint->languages)) {
            $this->context->addViolation(
                $constraint->message,
                array('%string%' => $value)
            );
        }
    }
}

That’s it! If it is not an accepted programming language, we add a new violation with the error message, which will be appended to the list.

If we follow the convention and the validator class has the same name as the constraint class appending “Validator” at the end, it will automatically work. In our previous example, we add a lang property to the Foo class and include the constraint in the class validation definition:

RaulFraile\Foo:
    properties:
        ...
        lang:
            - RaulFraile\Constraints\ProgrammingLanguage: ~

Voila! Our example will work if the lang property is not valid:

<?php

$obj = new Foo(); 
$obj->setEmail('raul@servergrove.com');
$obj->setAge(18);
$obj->setLang('perlang');

$violations = $validator->validate($obj);

Will output:

class Symfony\Component\Validator\ConstraintViolationList#11 (1) {
  private $violations =>
  array(1) {
    [0] =>
    class Symfony\Component\Validator\ConstraintViolation#33 (8) {
      private $message =>
      string(59) "The string "erlang" is not an accepted programming language"
      private $messageTemplate =>
      string(61) "The string "%string%" is not an accepted programming language"
      private $messageParameters =>
      array(1) {
        ...
      }
      private $messagePluralization =>
      NULL
      private $root =>
      class RaulFraile\Foo#2 (3) {
        ...
      }
      private $propertyPath =>
      string(4) "lang"
      private $invalidValue =>
      string(6) "erlang"
      private $code =>
      NULL
    }
  }
}

Who is using it

More info

Photo: Jack Spades – Security Features of the US Twenty Dollar Bill