Steven Brown

Symfony 2 Field Comparison Validator

by on Aug.25, 2011, under PHP, Symfony

Symfony 2 comes with a range of predefined validators you can use to validate your forms, however I recently came across the need to validate that one field is equal to another. This is actually quite common since most registration forms will require you to enter your email and/or password twice.

You could easily embed a custom validator within the form itself:

use Symfony\Component\Validator\Constraints as Assert;
 
class User
{
    public $password;
 
    public $confirmationPassword;
 
    /**
     * @Assert\True(message = "The password and confirmation password do not match")
     */
    public function isPasswordEqualToConfirmationPassword()
    {
        return ($this->password === $this->confirmationPassword);
    }
}

I don’t really like that the error is added to the form rather than the individual field, and since this comparison is used often it would be much easier if it could be easily added via annotation.

In order to do this, we need two classes. The first defines the validator annotation, also known as a constraint:

use Symfony\Component\Validator\Constraint;
 
/**
* @Annotation
*/
class EqualsField extends Constraint
{
    public $message = 'This value does not equal the {{ field }} field';
    public $field;
 
    /**
     * {@inheritDoc}
     */
    public function getDefaultOption()
    {
        return 'field';
    }
 
    /**
     * {@inheritDoc}
     */
    public function getRequiredOptions()
    {
        return array('field');
    }
}

There is only one option for this constraint, that’s the name of the field we want to compare the current field against. In order to accept this field, we want to make it required and make it the default option, which means it will be populated with the first argument in the annotation (I think).

Notice how when we set the message we can use Twig style tokens, in this case {{ field }} will hold the name of the field we are comparing against.

Next, we add the validator itself, which accepts the form value and the constraint above, performs the comparison and returns whether or not it is valid:

use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Constraint;
 
class EqualsFieldValidator extends ConstraintValidator
{
    /**
     * Checks if the passed value is valid.
     *
     * @param mixed      $value      The value that should be validated
     * @param Constraint $constraint The constrain for the validation
     *
     * @return Boolean Whether or not the value is valid
     */
    public function isValid($value, Constraint $constraint)
    {
        if ($value !== $this->context->getRoot()->get($constraint->field)->getData()) {
 
            $this->setMessage($constraint->message, array(
                '{{ field }}' => $constraint->field,
            ));
 
            return false;
        }
 
        return true;
    }
}

Things are a bit tricky here, we don’t receive the comparison field or even the form to work with. Instead we get the form from the execution context ($this->context->getRoot()), from this we can get the field ($constraint->field from the constraint class we just created), then we get the data from it.

If the two values are not equal, we set the message with the field name and return false, essentially saying it’s invalid.

Now you can easily implement the validator with annotations:

use Skjb\Component\Validator\Constraint\EqualsField;
 
class User
{
    public $password;
 
    /**
     * @EqualsField('password', message = "The password and confirmation password do not match")
     */
    public $confirmationPassword;
}

Notice how we can still make our own nice message, we could actually use the {{ field }} token here if we wanted to.

I imagine you could easily create similar validators for less than, greater than, multiple of, or any other situation where a field is validated against another field.

You can find the code for this post at https://github.com/skjb/symfony2

:, , , , , ,

Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!