Steven Musumeche
Steven Musumeche

Reputation: 2936

Symfony Embedded Form Conditional Validation

I have a form which contains three objects:

$builder
    ->add('customer', new CustomerType())
    ->add('shippingAddress', new AddressType())
    ->add('billingAddress', new AddressType())
    ->add('sameAsShipping', 'checkbox', ['mapped' => false])
;

Each of the embedded forms has their own validation constraints and they work. In my main form, I have cascade_validation => true so that all of the embedded form validation constraints are applied. This also works.

I am having trouble 'disabling' the validation on the billingAddress form if the sameAsShipping checkbox is enabled. I can't make the validation in the AddressType form conditional because it always needs to be enforced for the shippingAddress form.

Upvotes: 3

Views: 1095

Answers (2)

Aitch
Aitch

Reputation: 1697

Create a form model (I use it in nearly every form, but this code here is not tested):

/**
 * @Assert\GroupSequenceProvider()
 */
class YourForm implements GroupSequenceProviderInterface {
  /**
   * @Assert\Valid()
   */
  private $customer;

  /**
   * @Assert\Valid()
   */
  private $shippingAddress;

  /**
   * @Assert\Valid(groups={'BillingAddressRequired'})
   */
  private $billingAddress;

  private $billingSameAsShipping;

  public function getGroupSequence() {
    $groups = ['YourForm'];

    if(!$this->billingSameAsShipping) {
      $groups[] = 'BillingAddressRequired';
    }

    return $groups;
  }
}

Try to use meaningful names. sameAsShipping is hard to understand. Read the if-condition in getGroupSequence: if not billing (address) same as shipping (address) then billing address required.

That's all, clear code in my opinion.

Upvotes: 1

chh
chh

Reputation: 593

I've solved this same problem by using validation groups.

First, this is important: use the validation_groups option in your AddressType to set the validation groups of every constraint of each field in the type:

<?php

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Form\FormBuilderInterface;

class AddressType extends \Symfony\Component\Form\AbstractType
{
    function buildForm(FormBuilderInterface $builder, array $options)
    {
        $groups = $options['validation_groups'];

        $builder->add('firstName', 'text', ['constraints' => new Assert\NotBlank(['groups' => $groups])]);
        $builder->add('lastName', 'text', ['constraints' => new Assert\NotBlank(['groups' => $groups])]);
    }
}

Then, in the parent form pass different validation groups to the two fields:

<?php

$formBuilder = $this->get('form.factory')
    ->createNamedBuilder('checkout', 'form', null, [
        'cascade_validation' => true,
    ])
    ->add('billingAddress', 'address', [
        'validation_groups' => 'billingAddress'
    ])
    ->add('shippingAddress', 'address', [
        'validation_groups' => 'shippingAddress'
    ]);

Then, determine determine your validation groups by looking at the value of the checkbox.

if ($request->request->get('sameAsShipping')) {
    $checkoutValidationGroups = ['Default', 'billingAddress'];
} else {
    $checkoutValidationGroups = ['Default', 'billingAddress', 'shippingAddress'];
}

You can then validate only either the billingAddress or the shippingAddress, or both using the validation group mechanism.

I chose to use a button:

$formBuilder->add('submitButton', 'submit', ['validation_groups' => $checkoutValidationGroups]);

Upvotes: 1

Related Questions