Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Turn off validation for form type if checkbox is checked in Symfony

I'm trying to create an address form with a payment and shipping address. When a checkbox on the shipping address is checked, I want to skip form validation from occurring on that address.

I have created the form type below with a toggle option that will display and deal with the checkbox, however the form is still validated even when checked.

Symfony has documentation on how to implement such a form, and even though I almost have the exact same code, validation is not turned off when checked. I am not using validation groups, so I just disable the default group to disable validation on the entity.

The AddressType building a form for the Address class (which has annotation constraints on certain fields like NotBlank and Callback).

class AddressType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options["toggle"]) {
            $builder->add("toggle", CheckboxType::class, [
                "mapped" => false,
                "required" => false,
                "label" => $options["toggle"]
            ]);
        }

        $builder
            ->add("name", TextType::class, [
                "required" => !$options["toggle"]
            ])
            ->add("address", TextType::class, [
                "required" => !$options["toggle"]
            ])
            ->add("zipcode", TextType::class, [
                "label" => "Postcode",
                "required" => !$options["toggle"]
            ])
            ->add("city", TextType::class, [
                "required" => !$options["toggle"]
            ])
            ->add("countryCode", ChoiceType::class, [
                "choices" => Address::COUNTRY_CODES
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            "toggle" => false,
            "data_class" => Address::class,
            "validation_groups" => function(FormInterface $form) {
                if ($form->has("toggle") && $form->get("toggle")->getData() === true) {
                    return [];
                }

                return ["Default"];
            }
        ]);

        $resolver->setAllowedTypes("toggle", ["bool", "string"]);
    }
}

I use the type like this:

$addressForm = $this
    ->createFormBuilder([
        "paymentAddress" => $paymentAddress,
        "shippingAddress" => $shippingAddress
    ])
    ->add("paymentAddress", AddressType::class, [
        "label" => false
    ])
    ->add("shippingAddress", AddressType::class, [
        "label" => false,
        "toggle" => "Use payment address"
    ])
    ->add("submit", SubmitType::class, [
    ])
    ->getForm();

I've been going over this for a few hours now but I am not able to deduce why validation is not being turned off, and I am not willing to botch up a form over this little detail.

Why is validation for the AddressType not turned off by the closure in configureOptions? If this is just not how it works, what would be a better solution to accomplish partially turning off validation in a tidy way?

EDIT: Even if setting "validation_groups" => false in the defaults, in the children created in the builder, or in the usage of the form, validation will still happen. It does not have to do with the closure. Every online resource, including Symfony's own resource states that it should work though...

like image 440
Villermen Avatar asked Aug 16 '17 14:08

Villermen


1 Answers

[...] so I just disable the default group to disable validation on the entity.

Assuming that Address properties & constraints look like this:

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

// etc.

The "validator" assumes that these properties will be evaluated with Default group, because it always considers empty validation_groups (e.g. return [];) as ['Default'] (hence validation is not turned off when checked):

https://symfony.com/doc/current/validation/groups.html: If no groups are specified, all constraints that belong to the group Default will be applied.

A solution to accomplish partially turning off validation in a tidy way

Should there be many ways to achieve it, but I show you two of them:

  1. If none data_class is set to the root form, then Form group is available to validate this level only:

    $addressForm = $this
        ->createFormBuilder([...], [
            'validation_groups' => 'Form', // <--- set
        ])
    

    Next, in configureOptions method, set Address group as default and Form group if "toggle" is checked, also adds Valid() constraint for cascade validation:

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            // ...
            "validation_groups" => function(FormInterface $form) {
                if ($form->has("toggle") && $form->get("toggle")->getData() === true) {
                    return ['Form']; // <--- set
                }
    
                return ['Address']; // <--- set
            },
            'constraints' => [new Assert\Valid()], // <--- set
        ]);
    }
    

    That means, on submit with toggle off: Form and Address groups are applied to address fields, else, only Form group is applied.

  2. (Another way) In Address class add "Required" group to all constraints, it avoids validate these properties with Default group:

    /**
     * @Assert\NotBlank(groups={"Required"}) // <--- set
     */
    private $name;
    
    // etc.
    

    Next, in configureOptions method set Required as default group and Default if toggle is checked:

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            // ...
            "validation_groups" => function(FormInterface $form) {
                if ($form->has("toggle") && $form->get("toggle")->getData() === true) {
                    return ['Default']; // <--- set
                }
    
                return ['Required']; // <--- set
            },
            'constraints' => [new Assert\Valid()], // <--- set
        ]);
    }
    

    In this case, on submit with toggle off: Default and Required groups are applied to address fields, else only Default group, skipping thus the required fields.

Nested forms that contain objects disconnected from the root object can be validated by setting the constraints option to new Valid().

like image 86
yceruto Avatar answered Oct 06 '22 01:10

yceruto