Symfony 2.1, el libro oficial

12.2. Validando formularios

En la sección anterior, aprendiste cómo se envían los formularios con datos válidos o inválidos. En Symfony2, la validación se aplica sobre el objeto que está manejando el formulario (en este caso, el objeto de tipo Task). En otras palabras, no importa si el formulario es válido o no, lo que importa es que el objeto $task contenga información válida. El método $form->isValid() en realidad es un atajo que pregunta al objeto $task si tiene datos válidos o no.

La validación se realiza añadiendo un conjunto de reglas (llamadas constraints) a una clase. Como ejemplo, añade restricciones de validación para que el campo task no pueda estar vacío y para que el campo dueDate no pueda estar vacío y deba ser un objeto DateTime válido.

# Acme/TaskBundle/Resources/config/validation.yml
Acme\TaskBundle\Entity\Task:
    properties:
        task:
            - NotBlank: ~
        dueDate:
            - NotBlank: ~
            - Type: \DateTime
// Acme/TaskBundle/Entity/Task.php
use Symfony\Component\Validator\Constraints as Assert;

class Task
{
    /**
     * @Assert\NotBlank()
     */
    public $task;

    /**
     * @Assert\NotBlank()
     * @Assert\Type("\DateTime")
     */
    protected $dueDate;
}
<!-- Acme/TaskBundle/Resources/config/validation.xml -->
<?xml version="1.0" charset="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\TaskBundle\Entity\Task">
        <property name="task">
            <constraint name="NotBlank" />
        </property>
        <property name="dueDate">
            <constraint name="NotBlank" />
            <constraint name="Type">\DateTime</constraint>
        </property>
    </class>
</constraint-mapping>
// Acme/TaskBundle/Entity/Task.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;

class Task
{
    // ...

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('task', new NotBlank());

        $metadata->addPropertyConstraint('dueDate', new NotBlank());
        $metadata->addPropertyConstraint('dueDate', new Type('\DateTime'));
    }
}

¡Y ya está! Si ahora vuelves a enviar el formulario con datos no válidos, verás los correspondientes errores de validación en el formulario.

La validación es una característica muy poderosa de Symfony2 y tiene su propio capítulo.

12.2.1. Grupos de validación

Truco Si no utilizas grupos en la validación de tus entidades y objetos, puedes saltarte esta sección.

Si tu objeto utiliza la validación de grupos, tendrás que especificar qué grupo debe utilizar el formulario para validar la información:

$form = $this->createFormBuilder($users, array(
    'validation_groups' => array('registration'),
))->add(...);

Si además utilizas clases para definir los formularios (una buena práctica recomendada que se explica más adelante) entonces tendrás que agregar lo siguiente al método setDefaultOptions():

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'validation_groups' => array('registration'),
    ));
}

En ambos casos, sólo se utilizará el grupo de validación registration para validar el objeto manejado por el formulario.

12.2.2. Grupos que dependen de la información enviada

También es posible utilizar un callback o un closure en la opción validation_groups para determinar el grupo de validación que se aplica utilizando lógica PHP:

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'validation_groups' => array('Acme\\AcmeBundle\\Entity\\Client', 'determineValidationGroups'),
    ));
}

El código anterior hace que se ejecute el método estático determineValidationGroups()de la clase Client después de que haya comenzado el procesamiento del formulario enviado pero antes de que se ejecute la validación.

Este método recibe como argumento el objeto que representa al formulario. Si lo prefieres, puedes utilizar un closure en vez de el método de una clase para indicar toda la lógica directamente:

use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'validation_groups' => function(FormInterface $form) {
            $data = $form->getData();
            if (Entity\Client::TYPE_PERSON == $data->getType()) {
                return array('person');
            } else {
                return array('company');
            }
        },
    ));
}