Symfony validate form with mapped false form fields

kzrdt picture kzrdt · Oct 16, 2012 · Viewed 39.9k times · Source

I've got a form with extra fields added with the option mapped to false. But when I try to validate my form, it won't pass indicating "this value is not valid" above these specific form fields. Isn't this option supposed to bypass validation?

These form fields are only useful for populate other fields and I don't need to save or even check them.

The only solution I found is to remove all extra fields with js on a submit button click.

Answer

David Jacquel picture David Jacquel · Oct 26, 2012

This post is not up to date with Symfony 2.3

read comments bellow

A new version is comming !

Validating non mapped fields in Form (Symfony 2.1.2)

This is a global response for some stackoverflow questions about current way to validate unbounded or non mapped field in forms.

The rich Symfony 2 ecosystem makes our framework of choice a fast evolving tool.
Symfony 2.1 version brings a lot of deprecations. This means that what is working with Symfony 2.0 to 2.1.2 will no longer work in Symfony 2.3. For more information about this, read UPGRADE FROM Symfony 2.0 to 2.1 and read @deprecated comments in Symfony code.

Unbound fields

When building a form, you usually use Entities, and your validation can be made in the Entity itself tanks to Validation annotations.

namespace Dj\TestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**    
 * Dj\TestBundle\Entity\Post
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Dj\TestBundle\Entity\PostRepository")
 */
class Post
{
    // ... some code

    /**
    * @var string $title
    * @ORM\Column(name="title", type="string", length=200, nullable=false)
    * @Assert\NotBlank()
    */
    private $title;

    // .. getters and setters
}

But sometimes (often) you need to insert some fields in your form that are not mapped to the model.

Our model example is like this :

namespace Dj\TestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Dj\TestBundle\Entity\Post
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Dj\TestBundle\Entity\PostRepository")
 */
class Post
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**ghjkl
     * @var string $title
     * @ORM\Column(name="title", type="string", length=200, nullable=false)
     * @Assert\NotBlank()
     */
    private $title;

    // ... getters and setters
}

If we want to add an extra field called myExtraField to our Form we do :

class PostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('title')
                ->add('myExtraField', 'choice', array(
                        'label' => 'myExtraField option :',
                        'choices' => array(
                            1 => 'Option One',
                            2 => 'Option Wat !'
                        ),
                        'expanded' => true,
                        'mapped' => false
                   ));
    }
    // other methods
}

Note :

  • mapped replaces property_path that will be deprecated in Symfony 2.3
  • you can add a default selected value to myExtraField by adding a 'data' => 1 entry in your options array.

Example code :

$builder->add('title')
    ->add('myExtraField', 'choice', array(
        'label' => 'myExtraField option :',
        'choices' => array(
            1 => 'Option One',
            2 => 'Option Wat !'
        ),
        'data' => 1, // default selected option
        'expanded' => true,
        'mapped' => false
));

If you want to validate myExtraField field you can't do it in the Post Entity annotations, you have to do it in your form.

Validation non mapped field - the Symfony 2.0 way

The 2.0 way was to add a validator to the form builder ($builder->addValidator(..)), but this method is deprecated !

namespace Dj\TestBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// needed namespaces for 2.0 validation
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormError;

class PostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // ... $builder->add()

        // VALIDATING NON MAPPED FIELD Symfony 2.0 way
        /** @var Symfony\Component\Form\CallbackValidator $myExtraFieldValidator **/
        $myExtraFieldValidator = new CallbackValidator(function(FormInterface $form){
          $myExtraField = $form->get('myExtraField')->getData();
            if (empty($myExtraField)) {
              $form['myExtraField']->addError(new FormError("myExtraField must not be empty"));
            }
        });
        // adding the validator to the FormBuilderInterface
        $builder->addValidator($myExtraFieldValidator);
    }
    // ... other methods
}

This is currently validating the myExtraField field, BUT $builder->addValidator will die in Symfony 2.3 !

The Forward Compatible code

As stated in UPGRADE FROM Symfony 2.0 to 2.1, as the FormValidatorInterface is deprecated, we now have to pass our validation closure function to an event listener bound to FormEvents::POST_BIND event.

This is the code.

namespace Dj\TestBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// needed namespaces for 2.1 validation
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormError;

class PostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // ... $builder->add()

        // VALIDATING NON MAPPED FIELD Symfony 2.1.2 way (and forward)
        /** @var \closure $myExtraFieldValidator **/
        $myExtraFieldValidator = function(FormEvent $event){
            $form = $event->getForm();
            $myExtraField = $form->get('myExtraField')->getData();
            if (empty($myExtraField)) {
              $form['myExtraField']->addError(new FormError("myExtraField must not be empty"));
            }
        };

        // adding the validator to the FormBuilderInterface
        $builder->addEventListener(FormEvents::POST_BIND, $myExtraFieldValidator);
    }
    // ... other methods
}

This can certainly be improved with some Sf gurus help, but for now it's validates unbound form field in a forward compatible way.

Hope that helps unstuck some of us.

David