Dynamic Validation Groups Using a GroupSequenceProvider in Symfony2

June 12, 2012   

I recently had to build a form that had two possible validation groups. These groups were dependant on the data being submitted. Specifically, I had a file upload and a text input. The user could either enter a hash in the text field, which would then be validated using MinLength and other custom validators OR they could select a torrent file to upload. The file would need to be validated for mime type and size and also a custom validator for validating its contents.

There is no obvious way to accomplish this with standard validation_groups as it implies AND with everything in the group.

The solution is to use a GroupSequenceProvider. This is similar to a callback, but rather than performing the validation in the method itself, it is simply determining which validation groups to apply to this validation sequence. This means that, based on the data in the object, you can change the validation group at validation-time.

This is how to use a GroupSequenceProvider:

In your Resources/config/validation.xml file add the group-sequence-provider tag.

<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    <class name="SOTB\CoreBundle\Document\Torrent">
        <group-sequence-provider/>
        <!-- ...other properties and constraints here... -->
    </class>
</constraint-mapping>

Then on the class that you added the tag to, you must implement the GroupSequenceProviderInterface

<?php

namespace SOTB\CoreBundle\Document\Torrent;

use Symfony\Component\Validator\GroupSequenceProviderInterface;

class Torrent implements GroupSequenceProviderInterface
{
    /**
    * Returns which validation groups should be used for a certain state
    * of the object.
    *
    * @return array An array of validation groups
    */
    public function getGroupSequence()
    {
        $groups = array('always');
        if (!empty($this->fileName)) {
            array_push($groups, 'upload');
        }
        if (!empty($this->hash)) {
            array_push($groups, 'hash');
        }
        if (empty($this->fileName) && empty($this->hash)) {
            array_push($groups, 'either');
        }
        return $groups;
    }
}

In this example I have the validation groups always, upload, hash, either. I then specify these groups in my validation.xml for various checks.

For example, this only enforces a minimum hash length if a file was not uploaded but there’s always a max length.

<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    <class name="SOTB\CoreBundle\Document\Torrent">
        <group-sequence-provider/>
        <property name="hash">
        <constraint name="MaxLength">
            <option name="limit">1024</option>
            <option name="groups">
                <value>always</value>
            </option>
        </constraint>
        <constraint name="MinLength">
            <option name="limit">32</option>
            <option name="groups">
                <value>hash</value>
            </option>
        </constraint>
        </property>
    </class>
</constraint-mapping>

The pull request for this feature can be found here https://github.com/symfony/symfony/pull/3199. I don’t believe there is any documentation on it as of yet.