Symfony 2.3, el libro oficial

11.7. Secuencias de grupos de validación

En ocasiones puede resultar útil aplicar los grupos de validación paso a paso. Para ello, utiliza la opción GroupSequence. En el siguiente ejemplo, el objeto define una secuencia de grupos de validación, que determina el orden en el que los grupos de validación se deben validar.

Imagina que dispones de una clase llamada User y quieres validar que el nombre de usuario y la contraseña sean diferentes, pero solamente cuando todas las demás validaciones se hayan completado con éxito (para evitar mostrar demasiados mensajes de error).

# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\User:
    group_sequence:
        - User
        - Strict
    getters:
        passwordLegal:
            - "True":
                message: "La contraseña no puede ser igual que tu nombre de usuario"
                groups: [Strict]
    properties:
        username:
            - NotBlank: ~
        password:
            - NotBlank: ~
// src/Acme/BlogBundle/Entity/User.php
namespace Acme\BlogBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @Assert\GroupSequence({"Strict", "User"})
 */
class User implements UserInterface
{
    /**
    * @Assert\NotBlank
    */
    private $username;

    /**
    * @Assert\NotBlank
    */
    private $password;

    /**
     * @Assert\True(message="La contraseña no puede ser igual que tu nombre de usuario", groups={"Strict"})
     */
    public function isPasswordLegal()
    {
        return ($this->username !== $this->password);
    }
}
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\User">
    <property name="username">
        <constraint name="NotBlank" />
    </property>
    <property name="password">
        <constraint name="NotBlank" />
    </property>
    <getter property="passwordLegal">
        <constraint name="True">
            <option name="message">La contraseña no puede ser igual que tu nombre de usuario</option>
            <option name="groups">
                <value>Strict</value>
            </option>
        </constraint>
    </getter>
    <group-sequence>
        <value>User</value>
        <value>Strict</value>
    </group-sequence>
</class>
// src/Acme/BlogBundle/Entity/User.php
namespace Acme\BlogBundle\Entity;

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

class User
{
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('username', new Assert\NotBlank());
        $metadata->addPropertyConstraint('password', new Assert\NotBlank());

        $metadata->addGetterConstraint('passwordLegal', new Assert\True(array(
            'message' => 'La contraseña no puede ser igual que tu nombre de usuario',
            'groups'  => array('Strict'),
        )));

        $metadata->setGroupSequence(array('User', 'Strict'));
    }
}

En este ejemplo, primero se validan todas las restricciones del grupo User (que es el mismo que el grupo Default). Si todas esas restricciones pasan correctamente, entonces se aplica el grupo de validación Strict.

Advertencia Como se ha visto en la sección anterior, el grupo Default y el grupo cuyo nombre coincide con el nombre de la clase (en este ejemplo, User) son idénticos. No obstante, cuando se utilizan las secuencias de grupos, ya no son idénticos. El grupo Default ahora referencia a toda la secuencia en lugar de a las restricciones que no pertenecen a ningún otro grupo.

Esto significa que tienes que utilizar el grupo {ClassName} (en este caso, User) cuando especificas una secuencia. Si utilizas Default, el resultado es una recursión infinita, ya que el grupo Default hace referencia a la secuencia, que a su vez contiene el grupo Default, que referencia a la misma secuencia, etc.)

11.7.1. Proveedores de secuencias de grupos de validación

Imagina que dispones de una entidad User que puede corresponder a un usuario normal o a un usuario premium. Cuando se trate de un usuario premium, se deben aplicar algunas restricciones adicionales, como por ejemplo su tarjeta de crédito. Para determinar dinámicamente los grupos de validación que se deben activar, puedes crear un proveedor de secuencias de grupos de validación. Primero crea la entidad y define un nuevo grupo de validación llamado Premium:

# src/Acme/DemoBundle/Resources/config/validation.yml
Acme\DemoBundle\Entity\User:
    properties:
        name:
            - NotBlank: ~
        creditCard:
            - CardScheme:
                schemes: [VISA]
                groups:  [Premium]
// src/Acme/DemoBundle/Entity/User.php
namespace Acme\DemoBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class User
{
    // ...

    /**
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @Assert\CardScheme(
     *     schemes={"VISA"},
     *     groups={"Premium"},
     * )
     */
    private $creditCard;
}
<!-- src/Acme/DemoBundle/Resources/config/validation.xml -->
<?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="Acme\DemoBundle\Entity\User">
        <property name="name">
            <constraint name="NotBlank" />
        </property>

        <property name="creditCard">
            <constraint name="CardScheme">
                <option name="schemes">
                    <value>VISA</value>
                </option>
                <option name="groups">
                    <value>Premium</value>
                </option>
            </constraint>
        </property>
    </class>
</constraint-mapping>
// src/Acme/DemoBundle/Entity/User.php
namespace Acme\DemoBundle\Entity;

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

class User
{
    private $name;
    private $creditCard;

    // ...

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('name', new Assert\NotBlank());
        $metadata->addPropertyConstraint('creditCard', new Assert\CardScheme(
            'schemes' => array('VISA'),
            'groups'  => array('Premium'),
        ));
    }
}

A continuación modifica la clase User para que implemente la interfaz GroupSequenceProviderInterface y añade el métdo getGroupSequence(), que debe devolver un array con los grupos de validación a utilizar. Añade también la anotación @Assert\GroupSequenceProvider a la clase (o la opción group_sequence_provider: true al archivo YAML). Si existiera un método llamado isPremium() en la entidad que devuelve true si el usuario es premium, entonces el código resultante sería algo como:

// src/Acme/DemoBundle/Entity/User.php
namespace Acme\DemoBundle\Entity;

// ...
use Symfony\Component\Validator\GroupSequenceProviderInterface;

/**
 * @Assert\GroupSequenceProvider
 * ...
 */
class User implements GroupSequenceProviderInterface
{
    // ...

    public function getGroupSequence()
    {
        $groups = array('User');

        if ($this->isPremium()) {
            $groups[] = 'Premium';
        }

        return $groups;
    }
}