Web Development

Using Symfony Validators outside the Symfony Framework

While we are quite doing fine with our current framework, we are stuck in terms of data validation especially when validation is done deep inside several layers of model classes. I’ve been thinking of either using Zend Validate classes (ZF2) or Symfony Validators but it just happen that I learn Symfony validators first and a guide was available to use it outside the Symfony framework stack.

CodeIgniter Form Validation

We are currently using CodeIgniter as our application framework. While CodeIgniter’s Form Validation class is working fine for us in simple workflow, it doesn’t work well when it comes to these workflows:

  • Validating inside a model – well it works, but not well, see below
  • Re-using callback validators via class inheritance/sub-classing models
    • Ex: It seems that callbacks can only be read from the Controller
  • Validating data other than the POST data

So instead of using the built-in form validators (and we are not even sure if we are validating forms), I’ve searched for a good validation library and somehow, picked Symfony validators.

Installing Symfony Validators

For the CodeIgniter structure, Symfony validator library can be installed inside application/third_party. I used composer to install it.

cd application/third_party
mkdir symfony
composer require symfony/validator

To autoload the Symfony classes, we include the autoloader.

<?php
require_once APPPATH.'third_party/symfony/vendor/autoload.php';

// ...
?>

Integration – sample validation

In Symfony, validations involves the following components:

  • Entity – the object being validated
  • Constraints – the validation rules
  • Validation wrapper class
  • Violations – objectives containing the details of the violated rules

Entity

The entity class contains properties which are being validated by those validation constraints. See an example below.

<?php

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

class ProjectEntity
{
    protected $title;
    protected $description;
    protected $startDate;
    protected $endDate;

    public function __construct(array $data) {
        // Set property values from data array
        foreach ($data as $field => $value) {
            if (property_exists($this, $field)) {
                $this->{$field} = $value;
            }
        }
    }
}

// ...
?>

Constraints

Symfony validation component has several constraints than be used to enforce most of the validation rules that a web application might need. These constraints are usually defined inside the entity but can also be defined in a configuration file. For our example, we define them inside the entity class.

<?php

// Namespaces
class ProjectEntity
{
    // Properties and methods
    // ...

    public static function loadValidatorMetadata(ClassMetadata $metadata) {
        // Title assertions
        $metadata->addPropertyConstraint('title', new Assert\NotBlank(array(
            'message' => 'Title is required.'
        )));
        $metadata->addPropertyConstraint('title', new Assert\Length(array(
            'min' => 5, 
            'max' => 100,
        )));

        // Description assertions
        $metadata->addPropertyConstraint('description', new Assert\NotBlank(array(
            'message' => 'Description is required.'
        )));
        $metadata->addPropertyConstraint('description', new Assert\Length(array(
            'min' => 5, 
            'max' => 200,
        )));

        // Start date constraints
        $metadata->addPropertyConstraint('startDate', new Assert\NotBlank(array(
            'message' => 'Start date is required.'
        )));
        $metadata->addPropertyConstraint('startDate', new Assert\Date(array(
            'message' => 'Start date is invalid.',
        )));

        // End date constraints
        $metadata->addPropertyConstraint('endDate', new Assert\NotBlank(array(
            'message' => 'End date is required.'
        )));
        $metadata->addPropertyConstraint('endDate', new Assert\Date(array(
            'message' => 'End date is invalid.',
        )));

        $metadata->addGetterConstraint('isValidDateRange', new Assert\IsTrue(array(
            'message' => 'Start and end date range should be valid.',
        )));
    }

    /**
     * Validate start and end date range 
     * 
     * @return boolean
     */
    public function getIsValidDateRange() {
        $validStart = ($this->startDate && strtotime($this->startDate));
        $validEnd = ($this->endDate && strtotime($this->endDate));

        if ($validStart && $validEnd) {
            $startDate = new DateTime($this->startDate);
            $endDate = new DateTime($this->endDate);
            $today = new DateTime();
            
            if ($startDate < $endDate && $endDate > $today) {
                // End is greater than start and end is in the future date
                return true;
            }
        }

        return false;
    }
}

// ...
?>

The getIsValidDateRange() method is called by the isValidDateRange getter constraint to check if the start date and end date is a valid range and the end date must be a date in the future. Although there are callback validators for Symfony validator, I can’t make it work properly. If it was working, I should have been using them.

The advantage of using the getter constraint validator is that it is not specific to to a property. This can be used against one or more properties just like what we did on start date and end date. This stye can also be used to validate against data from a data base or a web service for example.

Validation object

Symfony validation has a validator builder that creates an object and initializes it. In Symfony framework, this is done automatically. However, since we are using the Symfony validator outside the framework, we need to initialize it ourselves.

<?php

use Symfony\Component\Validator\Validation;

$validator = Validation::createValidatorBuilder()
    ->addMethodMapping('loadValidatorMetadata')
    ->getValidator();

$data = array(
    'title' => 'Test Project 1',
    'description' => 'This is just a test project...',
    'startDate' => '2015-08-23',
    'endDate' => '2015-09-30',
);

$entity = new \ProjectEntity($data);
$violations = $validator->validate($entity);

// ...
?>

Violations

If there are validation rules violated, violations are returned. We can then extract the validation messages for each violations.

<?php

if (count($violations) > 0) {
    foreach ($violations as $violation) {
        echo $violation->getMessage().'<br>';
    }
} else {
    echo 'No violations.<br>';
}

// ...
?>

That’s it! Enjoy and share.

1 thought on “Using Symfony Validators outside the Symfony Framework”

Leave a reply

Your email address will not be published. Required fields are marked *