Symfony Embedded Form Conditional Validation
I have a form that contains three objects:
$builder
->add('customer', new CustomerType())
->add('shippingAddress', new AddressType())
->add('billingAddress', new AddressType())
->add('sameAsShipping', 'checkbox', ['mapped' => false])
;
Each of the built-in forms has their own validation constraints, and they work. In my main form I have cascade_validation => true
, so all the built-in form validation constraints apply. This also works.
I'm having a problem "disabling" validation on a form billingAddress
if a checkbox is sameAsShipping
enabled. I cannot make the validation on the AddressType conditional because it always needs to be applied to the shippingAddress form.
source to share
I solved this same problem using validation groups.
First, this is important: use the parameter validation_groups
in AddressType
to set the validation groups for each 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 the different validation groups into 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 define your validation groups by looking at the checkbox value.
if ($request->request->get('sameAsShipping')) {
$checkoutValidationGroups = ['Default', 'billingAddress'];
} else {
$checkoutValidationGroups = ['Default', 'billingAddress', 'shippingAddress'];
}
Then you can check only either billingAddress
, or shippingAddress
, or both using the check group mechanism.
I decided to use a button:
$formBuilder->add('submitButton', 'submit', ['validation_groups' => $checkoutValidationGroups]);
source to share
Create a form model (I use it in almost every form, but this code is not tested here):
/**
* @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
hard to understand. Read the if-condition in getGroupSequence
: if there is no billing (address), same as shipping (address), then the billing address must be specified.
That's all, clear code in my opinion.
source to share