Symfony 2.3, 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. Desactivando la validación

La posibilidad de establecer la opción validation_groups a false se añadió en la versión 2.3 de Symfony. En las versiones anteriores, puedes conseguir el mismo resultado si asignas un array vacío a la opción validation_groups.

En ocasiones puede resultar necesario desactivar completamente la validación de un formulario. Para ello, no incluyas la llamada al método isValid() en tu controlador. Si no puedes cambiar tu controlador, la alternativa consiste en establecer la opción validation_groups a false o a un array vacío:

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

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

Aunque hayas desactivado la validación, Symfony sigue realizando algunas comprobaciones básicas sobre el formulario, como por ejemplo si un archivo subido es demasiado grande o si se han enviado campos que no existían en el formulario original. Para desactivar completamente todas estas validaciones, utiliza el evento POST_SUBMIT de los formularios.

12.2.3. Grupos que dependen de la información enviada

También es posible utilizar un callback 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');
            }
        },
    ));
}

12.2.4. Grupos que dependen del botón pulsado

Esta característica solamente está disponible desde la versión 2.3 de Symfony.

Si tu formulario contiene varios botones, puedes modificar el grupo de validación dependiendo del botón pulsado para enviar el formulario. Considera por ejemplo un formulario multi-página que te permite avanzar al siguiente paso o volver al anterior. Supongamos que al volver al paso anterior, los datos del formulario se deben guardar pero no validar.

En primer lugar, añade los dos botones al formulario:

$form = $this->createFormBuilder($task)
    // ...
    ->add('nextStep', 'submit')
    ->add('previousStep', 'submit')
    ->getForm();

A continuación, configura los grupos de validación del botón para volver al paso anterior. Como en este caso lo que queremos es que no se realice ninguna validación, establece la opción validation_groups a false:

$form = $this->createFormBuilder($task)
    // ...
    ->add('previousStep', 'submit', array(
        'validation_groups' => false,
    ))
    ->getForm();

Ahora el formulario ya no comprobará las restricciones que has configurado para cada campo. No obstante, seguirá realizando las validaciones genéricas como comprobar si el archivo subido es demasiado grande o si has enviado una cadena de texto en un campo que debería ser numérico.