32

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.

Mohammed H
  • 6,880
  • 16
  • 81
  • 127
kzrdt
  • 422
  • 2
  • 6
  • 12

5 Answers5

74

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.

starball
  • 20,030
  • 7
  • 43
  • 238
David Jacquel
  • 51,670
  • 6
  • 121
  • 147
  • Amazing answer! Thanks so much for putting this all together, saved me a lot of time understanding what to do with my pre-SF2.1 forms' validation. – Victor Farazdagi Apr 02 '13 at 10:49
  • 1
    Victor, I'm really happy to know that hours spent reading SF2 code, understanding and writing all down is useful for someone. – David Jacquel Apr 10 '13 at 17:19
  • There has to be a better way... What if I want to add a validation constraint on an un-mapped field and just for a specific validation group ? Is that possible ? – Żabojad Jul 30 '13 at 16:28
  • 1
    Thanks, this is really helpful! One note, FormEvents::POST_BIND is deprecated as of Sf2.3, so people should use FormEvents::POST_SUBMIT now. – caponica Aug 26 '13 at 17:09
  • Very helpful indeed, thanks for providing some sensible documentation on sensiolabs product when they themselves in my opinion fail totally in doing so. What a messy update and arrogant approach to users! Their spirit is "This is now deprecated, good luck finding deep in our docs how it's done now" whereas your step by step instruction is what one could expect from a the creators of a framework where they totally break backwards compatibility in such a minor version diff as 2.0=>2.1. Sorry for ranting, again, your post is awesome. – Matt Welander Sep 25 '13 at 15:15
  • @DavidJacquel: The OP was asking why validation occurred when mapped is false. OP did not want validation. Your answer does not address the OP's (and my) question. – geoB Oct 13 '13 at 00:45
  • @geoB you're right the answer seems to be out of topic. But, I had the same problem as OP and my researches make me think that a full article on non mapped fields can be useful. In fact, I resolved the OP problem. Maybe I should have explained it better. – David Jacquel Oct 14 '13 at 13:25
  • @DavidJacquel: Turns out the original question was relevant for me because symfony >=2.3.5 caused unknown choice values to be declared invalid. The solution was not to add validity but to always provide the choice field with a set of values. – geoB Oct 14 '13 at 17:31
31

As I mentionned in a question on a similar topic, since Symfony 2.1, you should use the 'constraints' option to add validation to your un-mapped fields:

use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\NotBlank;

$builder
    ->add('firstName', 'text', array(
        'constraints' => new MinLength(3),
    ))
    ->add('lastName', 'text', array(
        'constraints' => array(
            new NotBlank(),
            new MinLength(3),
        ),
    ))
;

Hope it will help anybody like me who lost some time on this...

Community
  • 1
  • 1
Żabojad
  • 2,946
  • 2
  • 32
  • 39
5

If you are already using a constraint then you can do this:

$builder
    ->add('new', 'repeated', array(
            'type' => 'password',
            'required'          => true,                    
            'invalid_message' => 'crmpicco.status.password_mismatch',
            'constraints'       => array(
                new NotBlank(),
                new Assert\Length([
                    'min'        => 2,
                    'max'        => 50,
                    'minMessage' => 'Your first name must be at least 2  characters long',
                    'maxMessage' => 'Your first name cannot be longer than 2 characters',
                ])
            )
        ))
    ->add('save', 'submit', array(
            'label' => 'password.form.fields.save',
        ))
    ;
crmpicco
  • 16,605
  • 26
  • 134
  • 210
0

It's works. Checked for symfony 2.1. Code should be like this:

$builder->add('password', 'password', ['required' => false, 'mapped' => false]);

Of course property 'required' is not required. Example from the documentation.

0

Symfony 3.4 expression constraint based on non mapped fields :

$resolver->setDefaults([
        'data_class' => MyClass::class,
        'constraints' => [
            new Expression([
                'expression' => 'this["endTime"].getData() >= this["startTime"].getData()',
                'message' => 'L\'heure de fin doit être plus grande ou égale à l\'heure de début'
            ])
        ]
    ]);